Fork me on GitHub

Go语言程序的初始化和执行顺序

Go语言程序的初始化和执行总是从main.main函数开始的。

但是如果main包导入了其它的包,则会按照顺序将它们包含进main包里(这里的导入顺序依赖具体实现,一般可能是以文件名或包路径名的字符串顺序导入)。如果某个包被多次导入的话,在执行的时候只会导入一次。当一个包被导入时,如果它还导入了其它的包,则先将其它的包包含进来,然后创建和初始化这个包的常量和变量,再调用包里的init函数,如果一个包有多个init函数的话,调用顺序未定义(实现可能是以文件名的顺序调用),同一个文件内的多个init则是以出现的顺序依次调用(init不是普通函数,可以定义有多个,所以也不能被其它函数调用)。最后,当main包的所有包级常量、变量被创建和初始化完成,并且init函数被执行后,才会进入main.main函数,程序开始正常执行。

值得注意的是,需要避免循环导入包,防止形成死锁

下图是Go程序函数启动顺序的示意图:

由图可以看出,包的全局常量,全局变量的初始化时间早于init()函数。导入包的init函数的执行时间早于main.main()函数.

要注意的是,在main.main函数执行之前所有代码都运行在同一个goroutine,也就是程序的主系统线程中。因此,如果某个init函数内部用go关键字启动了新的goroutine的话,新的goroutine只有在进入main.main函数之后才可能被执行到。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"fmt"
)

var test = 1

func init() {
fmt.Println("enter into init")

go func() {
test = 2
fmt.Println("hello world in goroute")
}()

fmt.Println("exit from init")
}

func main() {
fmt.Println("1st test:",test)
time.Sleep(1)
fmt.Println("2nd test:",test)
}

输出结果:

enter into init
exit from init
1st test: 1
hello world in goroute

2nd test: 2

通过test的值可以看出,进入main.main后,test值并未更新为2,说明init中的goroutine并未执行。等待一段时间后,goroutine执行完毕,这个时候test的值就被更新了。

您的鼓励是我持之以恒的动力