Go程序真正首个执行的函数是平台相关汇编入口(如_rt0_amd64_linux),随后跳转至runtime.rt0_go,再经osinit、schedinit等初始化后才调用runtime.main;所有包级init函数均在main.main之前执行完毕。

runtime.main 不是第一个执行的函数
你写的 main.main 函数根本不是程序启动时第一条执行的 Go 代码。真正最先跑的是平台相关汇编入口(比如 _rt0_amd64_linux),它做完寄存器设置、栈对齐后,立刻跳转到 runtime.rt0_go。这个函数干完 TLS 初始化、g0/m0 构建、命令行参数保存后,就调用 runtime.schedinit —— 此时连你的包名都还没加载。
所有 init 函数在 main.main 调用前已全部执行完毕
runtime.main 内部只做三件事:启一个 signal goroutine、调 main.main、收尾退出。它不负责初始化,只负责“确认初始化已完成”。所以你在调试时如果发现程序卡住没进 main 第一行,问题一定出在 init 阶段:
- 某个包的
init函数里写了阻塞 I/O(比如未设超时的 HTTP 请求) - 跨包依赖中,A 包的
init等待 B 包的全局变量,而 B 包的init又反过来等 A 包 —— 死锁 - 向 nil map 或 nil slice 写入,panic 发生时堆栈里只有
runtime.doInit和包名,完全看不到main.main
main 函数签名错误会导致链接失败,不是运行时报错
Go 编译器允许你写 func main(args []string),但链接器(cmd/link)会在最后阶段找符号 main.main,且只认零参数零返回的版本。一旦签名不符,报错是:
undefined reference to `main.main'
常见踩坑点:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 测试文件(
*_test.go)里误写package main+func main(),导致构建时冲突 - 多个子命令放在同一目录下,都用了
package main,触发multiple main packages - 想传参给 main 做配置,结果改了签名,编译通过但链接炸了
os.Args 在 runtime.args 阶段就已解析完成
你调用 os.Args 时拿到的切片,其底层数据早在 runtime.rt0_go 就从寄存器里拷贝并保存好了,不是在 main.main 里动态读取的。这意味着:
-
os.Args[0]是二进制路径(./myapp),不是源文件名(main.go) - 如果你在
init函数里访问os.Args,是安全且有效的 —— 它已经 ready - 别试图在
main开头再 parse 一遍os.Args手动拆分,直接用flag或os/exec.Command更可靠
整个启动链最易被忽略的环节,其实是 runtime.schedinit 里对 P 池和内存分配器的初始化 —— 它们决定了后续所有 goroutine 的调度行为和堆分配效率。但你没法在 init 里干预这些,只能接受运行时默认策略。

















