Go应用程序的初始化是在单一的goroutine中执行的。对于包这一级别的初始化来说,在一个包里会先进行包级别变量的初始化。一个包下可以有多个init函数,每个文件也可以有多个init 函数,多个 init 函数按照它们的文件名顺序逐个初始化。但是程序不可能把所有代码都放在一个包里,通常都是会引入很多包。如果main包引入了pkg1包,pkg1包本身又导入了包pkg2,那么应用程序的初始化会按照什么顺序来执行呢?
对于这个初始化过程我粗略的画了一个示意图,理解起来更直观些。
图的上半部分表示了main包导入了pkg1包,pkg1包又导入了pkg2包这样一个包之间的依赖关系。图的下半部分表示了,这个应用初始化工作的执行时间顺序是从被导入的最深层包开始进行初始化,层层递出最后到main包,每个包内部的初始化程序依然是先执行包变量初始化再进行init函数的执行。
接下来我按照上图演示的包之间的依赖关系创建一个示例应用程序来论证一下上面的结论。
示例程序完整的代码已经传到了我在Github上写的开发笔记里,代码链接:https://github.com/kevinyan815/gocookbook/tree/master/codes/init_trace
为了追踪初始化过程,并输出有意义的日志,我在程序的trace包里定义了一个辅助方法,打印出日志并返回一个用来初始化的整数值:
package trace
import "fmt"
func Trace(t string, v int) int {
fmt.Println(t, ":", v)
return v
}
包pkg2里定义了两个包级别的变量和一个init函数,为了显示出他们的执行顺序init函数里只打印输出一下他们自己的名字和所在的包。
package pkg2
import (
"example.com/init_trace/trace"
"fmt"
)
var P2_v1 = trace.Trace("init P2_v1", 20)
var P2_v2 = trace.Trace("init P2_v2", 30)
func init() {
fmt.Println("init func in pkg2")
}
包pkg1里也是定义了两个包变量和一个init函数,为了引用pkg2,这里定义的变量是在pkg2变量的基础上增加了10。
package pkg1
import (
"example.com/init_trace/pkg2"
"example.com/init_trace/trace"
"fmt"
)
var P1_v1 = trace.Trace("init P1_v1", pkg2.P2_v1 + 10)
var P1_v2 = trace.Trace("init P1_v2", pkg2.P2_v2 + 10)
func init() {
fmt.Println("init func in pkg1")
}
在应用最外层的main包里,main.go文件中定义了两个init函数,以及两个包变量。两个包变量分别在包pkg1和包pkg2的变量值基础上增加10。
package main
import (
"example.com/init_trace/pkg1"
"example.com/init_trace/pkg2"
"example.com/init_trace/trace"
"fmt"
)
func init() {
fmt.Println("init1 func in main")
}
func init() {
fmt.Println("init2 func in main")
}
var M_v1 = trace.Trace("init M_v1", pkg1.P1_v2 + 10)
var M_v2 = trace.Trace("init M_v2", pkg2.P2_v2 + 10)
func main() {
fmt.Println("main func in main")
}
这么做也是为了验证即使包在一个应用里被导入了多次,它只会初始化一次。
按照我们上面的结论,这个程序执行前应该先对pkg2进行初始化,然后是pkg1包的初始化,最后是main的初始化,所有初始化工作完成后main函数才会被执行。
程序的运行结果如下:
init P2_v1 : 20
init P2_v2 : 30
init func in pkg2
init P1_v1 : 30
init P1_v2 : 40
init func in pkg1
init M_v1 : 50
init M_v2 : 40
init1 func in main
init2 func in main
main func in main
正是和上面结论里给出的结果完全一样,同时输出里只看到pkg2包进行了一次初始化,也论证了在应用里一个包可以被多次导入但只会初始化一次。
我再把今天文章里得到的结论总结一下,一共有五条:
包级别变量的初始化先于包内
init函数的执行。一个包下可以有多个
init函数,每个文件也可以有多个init函数。多个
init函数按照它们的文件名顺序逐个初始化。应用初始化时初始化工作的顺序是,从被导入的最深层包开始进行初始化,层层递出最后到
main包。不管包被导入多少次,包内的
init函数只会执行一次。应用在所有初始化工作完成后才会执行
main函数。
今天的文章比较简单,是我们平时做开发时一个不可忽略的小细节,虽然掌握了Go应用初始化工作的执行顺序,但是还是建议不要依靠init函数的执行顺序玩出什么骚操作和新花样,毕竟代码更多时候是写给人看的,规范合理易懂比炫技更重要。
近期文章推荐
感谢你能读到这里,下期会继续分享Go程序性能分析的第二趴,在
Echo和Gin框架里使用pprof分析API的性能,文章这两天写好了就发,请持续关注公众号「网管叨bi叨」。最后铁针儿们求关注、求转发、求在看,不要再说下次一定啦~!
- END -
关注公众号,获取更多精选技术原创文章

本文通过实例演示Go程序初始化过程及顺序。包级变量先于init函数执行,多个init函数按文件名顺序初始化。程序从最深层包开始,逐层初始化至main包。
65

被折叠的 条评论
为什么被折叠?



