此文转载自本人微信公众号:lishinho杂货铺

 

先说今天做任务的时候遇到一个场景,本地go build编译成功的项目,在测试环境时报错:

/data/app/app-job/app-job flag redefined: confpanic: /data/app/app-job/app-job flag redefined: confgoroutine 1 [running]:flag.(*FlagSet).Var(0xc00010a180, 0x1b6e960, 0x2a9be50, 0x19596d4, 0x4, 0x19690a8, 0x13) /usr/local/go/src/flag/flag.go:850 +0x4afflag.StringVar(...) /usr/local/go/src/flag/flag.go:759go-gateway/app/app-svr/app-show/interface/conf.init.0() /go/src/go-main/app/app-svr/app-show/interface/conf/conf.go:279 +0x94

 

大意是重复定义了一个配置,不过转到报错的地方发现并没有任何地方引用这个方法,不知道什么时候两个包同时引用到这里导致的重复定义。

 

后来才发现这个方法是golang中特殊的init()函数。

 

 

下面先做一道题体会一下,求代码输出:

package test

import "fmt"

var T int64 = a()

func init() {
	fmt.Println("init in main.go")
}

func a() int64 {
	fmt.Println("calling a()")
	return 2
}

func main() {
	fmt.Println("calling main")

}

 

 

 

 

 

 

 

答案是:

calling a()

calling init1() in main

calling init2() in main

calling main

 

 

 

一,Golang加载过程

 

 

在一个go文件中, 初始化顺序规则:

(1) 引入的包

(2) 当前包中的变量常量

(3) 当前包的init

(4) main函数

包A 引入包B , 包B又引入包C, 则包的初始化顺序为:C -> B -> A

 

如果main包还导入了其它的包,那么就会在编译时将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次。

当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行init函数。如果所有的导入包加载完毕,最后会对main包的包级常量和变量初始化,然后执行main包的init()函数,最后在main方法中作为程序的起点执行。

看这张图应该有很直观认识:

图片

 

 

 

 

二,init()与main()

 

init()函数

golang的init()函数可以出现在任意package中,并且可以在一个package出现多次。

main()函数

只能应用在main package,是程序执行的起点。

 

就今天的每日一题来说,很明显按照加载规则,应该先加载变量T所引用的a(),然后执行init(),初始化最后执行main()作为程序起点。

 

而我工作中遇到的问题应该是在go运行时加载包的过程中,有一个包中的init()调用了flag.StringVar(...),让人很难发现,而在原本的包逻辑中也有同名的声明,所以会有重复定义conf的panic出现