init函数在包被导入时执行且仅一次,按依赖顺序c→b→a初始化,跨文件顺序未定义,不可显式调用,常用于驱动注册等一次性操作。

init 函数在包被导入时执行,且仅执行一次
Go 的 init 函数不是按文件、也不是按调用触发的,而是按「包初始化阶段」触发——只要该包被成功导入(无论多少次、无论间接还是直接),它的所有 init 函数在整个程序生命周期中只执行一次。这是 Go 运行时保证的语义,不依赖开发者手动控制。
常见错误现象:import _ "xxx" 多次出现在不同文件里,误以为会多次执行 init;或者把 init 当成构造函数,在多个地方反复 import 同一个包后观察日志,发现只打了一次。
- 同一个包内可定义多个
init函数,它们按源码出现顺序执行 - 跨文件也遵循“先声明、后执行”原则:
go build会按文件名字典序(非编译顺序)统一收集,但 Go 规范不保证跨文件的init执行顺序,应避免相互依赖 - 如果包 A 导入包 B,B 又导入 C,则初始化顺序是 C → B → A,且每个包的
init仍只跑一遍
重复 import 不会导致多次 init,但 import 循环会编译失败
Go 编译器在构建依赖图时会去重,import "net/http" 在十个文件里写十遍,最终 net/http 包仍只初始化一次。真正危险的是隐式循环导入,比如 A → B → A,这时编译直接报错:import cycle not allowed,根本不会走到 init 阶段。
使用场景:常用于注册驱动(如 database/sql 的 _ "github.com/lib/pq")、预热全局变量、检查环境约束等一次性动作。
SDCMS-B2C商城网站管理系统是一个以php+MySQL进行开发的B2C商城网站源码。 本次更新如下: 【新增的功能】 1、模板引擎增加包含文件父路径过滤; 2、增加模板编辑保存功能过滤; 3、增加对统计代码参数的过滤 4、新增会员价设置(每个商品可以设置不同级不同价格) 5、将微信公众号授权提示页单独存放到data/wxtemp.php中,方便修改 【优化或修改】 1、修改了check_b
立即学习“go语言免费学习笔记(深入)”;
- 不要在
init里做耗时操作(如 HTTP 请求、文件扫描),它阻塞整个包初始化,可能拖慢启动 - 避免在
init中调用本包其他未初始化完成的变量(尤其是跨文件),容易读到零值 -
init不能带参数、不能有返回值、不能被显式调用,Go 会忽略所有签名不符的函数
测试时 init 执行时机与 go test 的包加载方式强相关
运行 go test ./... 时,每个被测包是独立加载并初始化的;但若用 go test -run=TestXxx 单独跑某个测试,只要该测试所在包没被其他测试提前加载过,init 仍只执行一次。不过,如果测试中用 exec.Command("go", "run", ...) 启动另一个 Go 程序,那个子进程会重新走一遍初始化流程——此时父进程的 init 对它完全无效。
- 测试文件(xxx_test.go)属于独立包,其
init和主包init互不影响 - 用
go test -count=2并不会重启进程,只是重复运行测试函数,init仍只有第一次生效 - 若测试中修改了全局状态(如 monkey patching),要小心
init设置的初始值是否被覆盖或绕过
CGO 或 plugin 场景下 init 行为有例外
启用 CGO 后,C 代码部分不参与 Go 的初始化流程;而用 plugin.Open 动态加载插件时,插件内部的 init 会在 Open 时首次执行——注意,这是插件包自己的初始化,和主程序无关,且每次 Open 同一个插件都会重新执行它的 init(因为插件是独立加载的镜像)。这不是 bug,是 plugin 设计使然。
- 普通二进制构建中不存在这个问题,
init严格一次 - 交叉编译或构建 tag(如
// +build ignore)会影响哪些文件参与编译,从而影响哪些init被包含进来 - 如果用了
go:linkname黑魔法绕过正常导入路径,可能导致某些init被跳过——这属于未定义行为,应避免
最易被忽略的一点:init 的执行发生在 main 函数之前,但早于任何 runtime 初始化(比如 os.Args 已就绪,但 runtime.GOMAXPROCS 还未应用默认值)。别在 init 里假设并发环境已稳定,更别依赖 init 的执行顺序来做逻辑分支。









