Go 的用户代码初始化流程与 C/C++ 类似,都是先初始化全局变量,然后才调应用的 main 函数,但这个顺序限定在 package 内部,即在 package 内部初始化顺序为:
const -> var -> init()
每个 package 只会初始化一次,package 之间的初始化顺序由 import 顺序决定,如下图所示:
简单验证 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
最后修改于 2019-04-24