Go 中 init 函数的执行顺序

FreeGuideOnline 最新 2026-07-06

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 导入了 pkgAmain.go 导入了 pkgB

执行顺序:pkgAinitpkgBinitmain 包的 initmain()

变量初始化与 init 的顺序

在同一个包内,包级变量的初始化按依赖顺序进行。init 函数在所有包级变量初始化完成之后才运行。因此 init 中可以安全使用已经被初始化的包级变量。

例子:

package demo

var a = 1
var b = a + 2

func init() {
    a = 3  // 修改变量值
}

变量 a 先初始化为 1,然后 b 计算为 3(因为 a=1),之后 inita 改为 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