vlambda博客
学习文章列表

Go语言init函数你必须记住的六个特征

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 语言必看书籍。

  • 回复「视频」,获取价值 5000 大洋的视频资料,内含实战项目(不外传)!

  • 回复「路线」,获取最新版 Go 知识图谱及学习、成长路线图。

  • 回复「面试题」,获取四哥精编的 Go 语言面试题,含解析。

  • 回复「后台」,获取后台开发必看 10 本书籍。



对了,看完文章,记得点击下方的卡片。关注我哦~ 👇👇👇

如果您的朋友也在学习 Go 语言,相信这篇文章对 TA 有帮助 ,欢迎 转发分享 给 TA,非常感谢!