runsisi's

technical notes

go 程序初始化

2019-04-24 runsisigolang

Go 的用户代码初始化流程与 C/C++ 类似,都是先初始化全局变量,然后才调应用的 main 函数,但这个顺序限定在 package 内部,即在 package 内部初始化顺序为:

const -> var -> init()

每个 package 只会初始化一次,package 之间的初始化顺序由 import 顺序决定,如下图所示:

init

简单验证 import 顺序关系影响变量初始化顺序的代码如下:

~$ tree
.
├── go.mod
├── main.go
├── pkga
│   └── a.go
├── pkgb
│   └── b.go
└── pkgc
    └── c.go

pkga/a.go

package pkga

import (
	"fmt"
)

const ConstA = 'A'

var VarA = initVarA()

func initVarA() rune {
	fmt.Println("-- VarA init()")
	return ConstA
}

func init() {
	fmt.Println("-- pkga init()")
	fmt.Printf("VarA = %c\n", VarA)
}

pkgb/b.go

package pkgb

import (
	"fmt"

	"init/pkga"
)

const ConstB = 'B'

var VarB = initVarB()

func initVarB() rune {
	fmt.Println("-- VarB init()")
	fmt.Printf("VarA = %c\n", pkga.VarA)
	return ConstB
}

func init() {
	fmt.Println("-- pkgb init()")
	fmt.Printf("VarB = %c\n", VarB)
}

pkgc/c.go

package pkgc

import (
	"fmt"

	"init/pkga"
)

const ConstC = 'C'

var VarC = initVarC()

func initVarC() rune {
	fmt.Println("-- VarC init()")
	fmt.Printf("VarA = %c\n", pkga.VarA)
	return ConstC
}

func init() {
	fmt.Println("-- pkgc init()")
	fmt.Printf("VarC = %c\n", VarC)
}

main.go,其中 import 顺序为 pkgb -> pkgc

package main

import (
	"fmt"

	"init/pkgb"
	"init/pkgc"
)

const ConstM = 'M'

var VarM = initVarM()

func initVarM() rune {
	fmt.Println("-- VarM init()")
	fmt.Printf("ConstM = %c\n", ConstM)
	return pkgc.VarC
}

func main() {
	fmt.Println("-- main():")
	fmt.Printf("VarM = %c\n", VarM)
	fmt.Printf("VarB = %c\n", pkgb.VarB)
	fmt.Printf("VarC = %c\n", pkgc.VarC)
}

输出如下,初始化顺序为 pkga -> pkgb -> pkgc -> package main -> func main:

~$ go run main.go 
-- VarA init()
-- pkga init()
VarA = A
-- VarB init()
VarA = A
-- pkgb init()
VarB = B
-- VarC init()
VarA = A
-- pkgc init()
VarC = C
-- VarM init()
ConstM = M
-- main():
VarM = C
VarB = B
VarC = C

main.go,其中 import 顺序为 pkgc -> pkgb

package main

import (
	"fmt"

	"init/pkgc"
	"init/pkgb"
)

const ConstM = 'M'

var VarM = initVarM()

func initVarM() rune {
	fmt.Println("-- VarM init()")
	fmt.Printf("ConstM = %c\n", ConstM)
	return pkgc.VarC
}

func main() {
	fmt.Println("-- main():")
	fmt.Printf("VarM = %c\n", VarM)
	fmt.Printf("VarB = %c\n", pkgb.VarB)
	fmt.Printf("VarC = %c\n", pkgc.VarC)
}

输出如下,初始化顺序为 pkga -> pkgc -> pkgb -> package main -> func main:

~$ go run main.go 
-- VarA init()
-- pkga init()
VarA = A
-- VarC init()
VarA = A
-- pkgc init()
VarC = C
-- VarB init()
VarA = A
-- pkgb init()
VarB = B
-- VarM init()
ConstM = M
-- main():
VarM = C
VarB = B
VarC = C

注意如下的代码是非法的:

package main

import (
	"fmt"
)

const ConstA1 = '1'
const ConstA2 = '2'

var VarA1 = initVarA1()
var VarA2 = initVarA2()

func initVarA1() rune {
	fmt.Println("-- VarA1 init()")
	VarA2 = ConstA2
	return VarA2
}

func initVarA2() rune {
	fmt.Println("-- VarA2 init()")
	VarA1 = ConstA1
	return VarA1
}

func init() {
	fmt.Println("-- pkga init()")
	fmt.Printf("VarA1 = %c\n", VarA1)
	fmt.Printf("VarA2 = %c\n", VarA2)
}

func main() {

}

会报如下的错误,这也是 Go 不支持循环 import 的一个原因:

~$ go run main.go
# command-line-arguments
./main.go:10:5: initialization loop:
        /home/runsisi/working/test/loop/main.go:10:5 VarA1 refers to
        /home/runsisi/working/test/loop/main.go:13:6 initVarA1 refers to
        /home/runsisi/working/test/loop/main.go:11:5 VarA2 refers to
        /home/runsisi/working/test/loop/main.go:19:6 initVarA2 refers to
        /home/runsisi/working/test/loop/main.go:10:5 VarA1

参考资料

init functions in Go

https://medium.com/golangspec/init-functions-in-go-eac191b3860a

When is the init() function run?

https://stackoverflow.com/questions/24790175/when-is-the-init-function-run

Go, without package scoped variables

https://dave.cheney.net/2017/06/11/go-without-package-scoped-variables