序言
本文通过编写demo,解释说明golang init函数的执行顺序以及特点。
1. init的作用以及执行特点
Golang 中的 init 函数是每个 Go 文件默认存在的,而且可以有多个,顾名思意,init 函数的作用就是为程序初始化提供一个入口。在不同的包和 Go 文件中,init 函数的执行顺序是有一定规则的。最近在工作中遇到了一些与初始化相关的问题,于是这里编写了一个 demo 来验证 init 函数的执行顺序。想快速了解的同学,可以直接查看最后的图解,它清晰地展示了 init 函数的执行流程和优先级。
1.1 同一个go文件中可以有零个或多个init函数,其中他们的执行顺序与init代码的前后顺序有关
1.1.1 测试案例1——测试同一个go文件多个init的执行顺序
package main
import (
"fmt"
)
func init() {
fmt.Println(" init func 1 starting")
}
func init() {
fmt.Println(" init func 2 starting")
}
func init() {
fmt.Println(" init func 3 starting")
}
func main() {
fmt.Println(" main func starting")
}
从执行结果可以得出init方法的执行顺序是按照代码位置顺序来执行的
这时候我们把第三个init的代码顺序移动到2前面去
package main
import (
"fmt"
)
func init() {
fmt.Println(" init func 1 starting")
}
func init() {
fmt.Println(" init func 3 starting")
}
func init() {
fmt.Println(" init func 2 starting")
}
func main() {
fmt.Println(" main func starting")
}
可以看见执行结果
由此可以证明1.1的结论
1.2 同一个包里,init的执行是以包为单位的,只要某个包的函数在其他包被使用,那么这个包内所有go文件的所有init函数都执行,顺序就是go文件排列顺序,并且与该使用的函数在哪个go文件无关。
1.2.1 测试案例2
目录结构
测试代码
// main包的main.go文件
package main
import (
"fmt"
"go_test/testInit/testinitOne"
)
func main() {
testinitOne.NewTestInit()
fmt.Println(" main func starting")
}
// testinitOne包的1.go文件
package testinitOne
import "fmt"
func init() {
fmt.Println(" 这是 testInitOne 包的1.go文件的init执行")
}
// testinitOne包的1a.go文件,2.go和a.go以及b.go输出同理
package testinitOne
import "fmt"
func init() {
fmt.Println(" 这是 testInitOne 包的1a.go文件的init执行")
}
执行main函数后输出结果,可以看见执行结果与文件顺序相同,并且没有因为NewTestInit()函数在b.go就执行b.go文件的init,所以说,init的执行是以包为单位的,而不是看被外部使用的方法在哪个go文件就先执行哪个go文件的init。
1.3 有关包级常量和变量执行顺序:常量的初始化->包级的变量的初始化->init
1.3.1 测试案例3
目录结构
代码
// main.go 代码
package main
import (
"fmt"
"go_test/testInit/testConstAndVar"
)
func main() {
testConstAndVar.UserPackageTestConstAndVar()
fmt.Println("main starting")
}
// testInit代码
package testConstAndVar
import "fmt"
const PackageName = "const testConstAndVar init finish"
var VarName = func() string {
fmt.Println(PackageName) // 成功打印出来了说明常量以及初始化完了,之后才初始化包级变量
fmt.Println("now is init var : testConstAndVar.vatName")
return "varName"
}()
func UserPackageTestConstAndVar() {
fmt.Println("UserPackageTestConstAndVar() starting")
}
执行结果
2. 大全套案例,贯通初始化流程
目录结构
代码:
/**********testInitA包*****************/
// 1.go
package testInitA
import "fmt"
func init() {
fmt.Println("testInitA 1.go 第一个init函数 init")
}
func init() {
fmt.Println("testInitA 1.go 第二个init函数 init")
}
//const A1 = A2 + 1
//const A2 = A1 + 1
const testInitString1GO = "testInitA包的1.go文件的常量初始化完毕"
var testInitA1GOVar = func() string {
fmt.Println(testInitString1GO)
fmt.Println("testInitA包的1.go文件的包级变量testInitA1GOVar初始化")
return "testInitA包的1.go文件的变量"
}()
// 1A.go
package testInitA
import "fmt"
func init() {
fmt.Println("testInitA 1A.go init")
}
// 1B.go
package testInitA
import "fmt"
func init() {
fmt.Println("testInitA 1B.go init")
}
// 1C.go
package testInitA
import "fmt"
func init() {
fmt.Println("testInitA 1C.go init")
}
const testInitString1CGO = "testInitA包的1C.go文件的常量初始化完毕"
var testInitA1CGOVar = func() string {
fmt.Println(testInitString1CGO)
fmt.Println("testInitA包的1C.go文件的包级变量testInitA1CGOVar初始化")
return "testInitA包的1C.go文件的变量"
}()
func NewPackageTestInitA() {
}
/*******testInitB包*******/
// 1.go
package testInitB
import "fmt"
func init() {
fmt.Println("testInitB 1.go init")
}
//1A.go
package testInitB
import "fmt"
const testInitString1AGO = "testInitB包的1A.go文件的常量初始化完毕"
var testInitB1AGOVar = func() string {
fmt.Println(testInitString1AGO)
fmt.Println("testInitB包的1A.go文件的包级变量testInitB1AGOVar初始化")
return "testInitB包的1A.go文件的变量"
}()
func init() {
fmt.Println("testInitB 1B.go init")
}
func NewPackageTestInitB() {
}
/********testInit包******/
// main.go
package main
import (
"fmt"
"go_test/testInit/testInitA"
"go_test/testInit/testInitB"
)
const testMainConstInit = "testInit包的main.go文件的常量初始化完毕"
var testMainConstInitVar = func() string {
fmt.Println(testMainConstInit)
fmt.Println("testInit包的main.go文件的包级变量testMainConstInitVar初始化")
return "testInit包的的main.go文件的变量"
}()
func init() {
fmt.Println("main.go init1")
}
func init() {
fmt.Println("main.go init2")
}
func main() {
testInitA.NewPackageTestInitA()
testInitB.NewPackageTestInitB()
fmt.Println("main.go starting")
}
执行结果
可以看见这里的执行顺序是按照const->var->init来执行的,这里需要注意到是如下两行const是一起初始化的,而不是按照如下打印的顺序,因为没想到什么更好的测试办法,所以对于const的初始化结果是采取直接打印来判断的,这里读者需要清除,同一个包下的所有const需要都初始化完毕,才会进行到var初始化,而不是按照如下图的打印顺序,除此之外图中其他的打印顺序即是初始化的顺序。
这里对于const初始化不难想到一个要点,const初始化如果之间有相互依赖,类似于包的依赖,也是先初始化不依赖其他const的const,同理const也有可能初出现像包一样的循环依赖的问题,如下图就会出现循环依赖的问题,编译不会通过,不光const、package,var也是一样的,所有发生循环依赖的地方编译都不会通过。
const A1 = A2 + 1
const A2 = A1 + 1