在 golang 中,可执行文档的入口函数并不是我们写的 main 函数,编译器在编译 go 代码时会插入一段起引导作用的汇编代码,它引导进程进行命令行参数、运行时的初始化,例如内存分配器初始化、垃圾回收器初始化、协程调度器的初始化。golang 引导初始化之后就会进入用户逻辑,因为存在特殊的 init 函数,main 函数也不是进程最开始执行的函数。
一、golang 进程启动流程
golang 可执行进程由于运行时 runtime 的存在,其启动过程还是非常复杂的,这里通过 gdb 调试工具简单查看其启动流程:
1
2
3
4
5
6
7
CALL runtime·args(SB)
CALL runtime·osinit(SB)
CALL runtime·schedinit(SB)
CALL runtime·newproc(SB)
CALL runtime·mstart(SB)
二、特殊的 init 函数
- init 函数先于 main 函数自动执行,不能被其他函数调用
- init 函数没有输入参数、没有返回值
- 每个包可以含有多个同名的 init 函数,每个源文档也可以有多个同名的 init 函数
-
执行顺序 变量初始化 > init 函数 > main 函数。在复杂项目中,初始化的顺序如下:
- 先初始化 import 包的变量,然后先初始化 import 的包中的 init 函数,,再初始化 main 包变量,最后执行 main 包的 init 函数
- 从上到下初始化导入的包(执行 init 函数),遇到依赖关系,先初始化没有依赖的包
- 从上到下初始化导入包中的变量,遇到依赖,先执行没有依赖的变量初始化
- main 包本身变量的初始化,main 包本身的 init 函数
- 同一个包中不同源文档的初始化是按照源文档名称的字典序
util.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package util
import (
"fmt"
)
var c int = func() int {
fmt.Println("util variable init")
return 3
}()
func init() {
fmt.Println("call util.init")
}
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main
import (
"fmt"
_ "util"
)
var a int = func() int {
fmt.Println("main variable init")
return 3
}()
func init() {
fmt.Println("call main.init")
}
func main() {
fmt.Println("call main.main")
}
执行结果:
util variable init
call util.init
main variable init
call main.init
call main.main
参考:《Go 语言学习笔记 13、14、15 章》