本文代码地址:go 程序初始化
一、package 的作用
1.1 包是什么
包是 golang 的最小组织单元 , 每个.go文件的第一行必须是package 包名(明确回答了我是谁的问题),一个目录下的所有.go文件必须属于同一个包
注意看我这个目录结构:
bash
[vect@ubuntu-dev ~/golang/priciple/go_tour]$ tree
.
├── go.mod
├── main.go
├── package1
│ └── package1.go
├── package2
│ └── package2.go
└── utils
└── utils.go
4 directories, 5 files
go_tour 是入口包,生成可执行文件
go_tour/package1 是普通库包,被 main 引用
go_tour/package2是普通库包,被 package1 引用
go_tour/utils 是工具包,被多个包引用
1.2 包 = 命名空间 + 访问控制
golang
var Val1 = ... // 大写开头 → 导出,其他包可用:package2.Val1
var val1 = ... // 小写开头 → 私有,仅本包内可用
golang 所有的设计都是如此,不论是变量、函数、方法等,只要首字母大写,就表明导出,可以被其他包使用
二、init 函数的作用
2.1 init 是什么
init() 是 golang 的自动初始化函数,不需要手动调用,golang 运行时自动执行,有如下特点:
- 无参无返回值
- 不能被其它函数调用(
main也不行) - 一个包内可以有多个
init(),按照源文件字母序->同一文件内从上到下一次执行的顺序
例如,我的main.go中有两个init():
golang
func init() { fmt.Println("init func1 in main") } // 先执行
func init() { fmt.Println("init func2 in main") } // 后执行
2.2 init 的用途
- 注册驱动
- 初始化全局配置
- 校验环境变量
这些动作只需要做一次,所以,不管包被导入多少次,包内的init()只会执行一次
三、golang 程序初始化全流程
初始化遵循依赖图,按照被依赖者优先原则:
从叶子节点(无依赖的utils 和 package2)开始初始化,逐层往上
每个包内又分两步:
- 包内变量初始化,按照声明顺序
- 执行
init()函数

按照详细的顺序来:
text
程序启动
│
├─► 导入 main,发现 import "go_tour/package1"
│ │
│ ├─► 导入 package1,发现 import "go_tour/package2" 和 "go_tour/utils"
│ │ │
│ │ ├─► 导入 package2,发现 import "go_tour/utils"
│ │ │ │
│ │ │ ├─► 导入 utils(无依赖,最底层)
│ │ │ │ └─► utils 包初始化完毕
│ │ │ │
│ │ │ ├─► 初始化 package2 包级变量:Val1, Val2
│ │ │ └─► 执行 package2 的 init()
│ │ │
│ │ ├─► utils 已初始化,复用(不重复执行)
│ │ ├─► 初始化 package1 包级变量:V1, V2
│ │ └─► 执行 package1 的 init()
│ │
│ ├─► 初始化 main 包级变量:MainVal1, MainVal2
│ └─► 执行 main 的 init()
│
└─► 执行 main()
注意以下关键要点:
| 规则 | 说明 |
|---|---|
| 每个包只初始化一次 | utils 被 package1 和 package2 同时引用,但 TraceLog 输出只出现 6 次(而非更多),因为 utils 的变量和 init 只跑一遍 |
| 依赖先行 | 被 import 的包一定先于 import 它的包初始化 |
| 变量先于 init | 同一包内,包级变量初始化 → init() → 这才轮到被其他包引用 |
| 禁止循环依赖 | 如果 A import B 且 B import A,编译直接报错 |
_ 导入也会触发初始化 |
import _ "pkg" 不引用任何导出符号,但仍会执行该包的变量初始化和 init() |
done~