Go 中 init 函数的执行顺序
go package mypackage
import "fmt"
var Message string
func init() { fmt.Println("mypackage init 1") Message = "initialized" }
func init() { fmt.Println("mypackage init 2") }
## 单文件内多个 `init` 的执行顺序
**规则:按照 `init` 函数在源代码中出现的先后顺序依次执行。**
```go
package main
import "fmt"
func init() {
fmt.Println("第一个 init")
}
func init() {
fmt.Println("第二个 init")
}
func main() {
fmt.Println("main")
}
输出:
第一个 init
第二个 init
main
同一包内多个文件的 init 执行顺序
Go 语言规范没有严格规定同一包内不同文件的 init 执行顺序,但编译器通常按照文件名词法顺序处理传递给编译器的文件。为了保证可预测性,应当避免在同一包的不同文件之间依赖 init 的执行顺序。
如果必须在同一包内控制顺序,最稳妥的方式是将所有初始化逻辑放在同一个文件内,并显式按顺序调用。
包之间的 init 执行顺序
规则:如果一个包导入了其他包,被导入包的 init 函数一定先于当前包的 init 函数执行。
这形成了一张有向无环图(DAG),Go 运行时会做拓扑排序:先初始化无依赖的包,再逐层初始化上层包,最后初始化 main 包。
示例目录结构:
main.go
pkgA/a.go
pkgB/b.go
pkgA/a.go 无依赖;pkgB/b.go 导入了 pkgA;main.go 导入了 pkgB。
执行顺序:pkgA 的 init → pkgB 的 init → main 包的 init → main()。
变量初始化与 init 的顺序
在同一个包内,包级变量的初始化按依赖顺序进行。init 函数在所有包级变量初始化完成之后才运行。因此 init 中可以安全使用已经被初始化的包级变量。
例子:
package demo
var a = 1
var b = a + 2
func init() {
a = 3 // 修改变量值
}
变量 a 先初始化为 1,然后 b 计算为 3(因为 a=1),之后 init 将 a 改为 3。最终 a=3, b=3。
循环导入与 init 陷阱
Go 禁止包之间的循环导入。如果出现循环依赖,编译器会报错,根本轮不到 init 执行。
但即使没有循环依赖,也可能出现逻辑上的初始化顺序陷阱:假设包 C 导入包 D,包 D 的 init 使用了包 C 中还未初始化的值,这不会发生,因为依赖方向决定 D 先于 C 初始化。正确的依赖方向是关键。
完整示例演示
假设有如下包结构:
main.go
logger/logger.go
db/db.go
logger/logger.go(最底层,无依赖):
package logger
import "fmt"
func init() {
fmt.Println("logger init")
}
db/db.go 导入了 logger:
package db
import (
"fmt"
"yourmodule/logger"
)
func init() {
fmt.Println("db init")
// 可以安全使用 logger 包的内容
}
main.go 导入了 db:
package main
import (
"fmt"
"yourmodule/db"
)
func init() {
fmt.Println("main init")
}
func main() {
fmt.Println("main func")
}
执行 go run main.go 输出:
logger init
db init
main init
main func