文章目录
一、init() 函数什么时候执行?
在Go语言中,init 函数是一个特殊的函数,它在包被导入时自动执行,主要用于完成程序的初始化工作。例如初始化包级别的变量,或者执行包初始化时仅需执行一次的设置,如初始化资源、初始化数据库连接、载入本地配置文件、预先计算一些值、注册等。
在 golang 中, init() 函数是在main() 函数之前执行的。
package main
func init() {
println("init function called")
}
func main() {
println("main function called")
}
运行结果:
二、init() 函数特点
- init() 函数是可选的,源文件可以没有 init() 函数。
- init() 函数没有入参和返回值,自动执行,不能被其他函数调用。(与 main 函数一样)
- 同一个包,甚至是同一个源文件可以有多个 init() 函数。
- 不管包被导入多少次,包内的 init 函数只会执行一次。
三、代码执行顺序
Go语言代码执行顺序为:import –> const –> var –>init()–>main()。
1、初始化所有被导入的包;
2、初始化被导入的包所有全局常量、变量;
3、执行被导入的包 init() 函数;
4、层层递出最后执行 main() 函数。
需要注意的点:
- 程序初始化的顺序是,从被导入的最深层包开始进行初始化,层层递出最后到 main 包;
- Go的依赖分析器会尽可能并行地初始化包;
- 程序的初始化和执行都起始于 main 包。如果 main 包还导入了其它的包,那么就会在编译时将它们依次导入;
- 有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到 fmt 包,但它只会被导入一次,因为没有必要导入多次);
- 不同包的init函数按照包导入的依赖关系决定执行顺序。
- 在编码时避免依赖 init() 函数的执行顺序。虽然 init() 顺序是明确的,但代码可以更改,init() 函数之间的关系可能会使代码变得脆弱和容易出错,因此在编码时避免依赖 init() 函数的执行顺序。
// a 包
// a.go
package a
import _ "myGoProject/init_demo/b"
var a = func() int {
println("init int a")
return 'a'
}()
func init() {
println("a init function called")
}
// b 包
// b.go
package b
import _ "myGoProject/init_demo/c"
var b = func() int {
println("init int b")
return 'b'
}()
func init() {
println("b init function called")
}
// c 包
// c.go
package c
var c = func() int {
println("init int c")
return 'c'
}()
func init() {
println("c init function called")
}
// main 包
// main.go
package main
import (
_ "myGoProject/init_demo/a"
)
var m = func() int {
println("init int m")
return 1
}()
func init() {
println("init function called")
}
func main() {
println("main function called")
}
运行结果:
结论:包级变量是在 init 函数之前完成初始化的。
四、多个 init() 函数执行顺序
通过上面的内容我们可以知道,一个源文件可以有多个 init 函数,一个包也可以有多个 init 函数。 那么不同包、不同源文件中的多个 init 函数是按照什么顺序执行的呢?下面,我们通过一个个的小 demo 去分析。
1、一个源文件中多个 init() 函数
main.go 源文件有三个 init() 函数。
package main
func init() {
println("a init function called")
}
func init() {
println("b init function called")
}
func init() {
println("c init function called")
}
func main() {
println("main function called")
}
运行结果:
结论:一个源文件中多个 init() 函数的执行顺序是根据定义顺序确定,从上到下。
验证:把 3个 init() 函数的定义顺序改为 b、a、c。运行结果如下,执行顺序为 b、a、c。结论正确。
package main
func init() {
println("b init function called")
}
func init() {
println("a init function called")
}
func init() {
println("c init function called")
}
func main() {
println("main function called")
}
2、一个包中多个 init() 函数
main 包有4个源文件 a.go、b.go、c.go、main.go。
// a.go
package main
func init() {
println("a init function called")
}
// b.go
package main
func init() {
println("b init function called")
}
// c.go
package main
func init() {
println("c init function called")
}
// main.go
package main
func init() {
println("init function called")
}
func main() {
println("main function called")
}
运行结果:
结论:一个包中多个 init() 函数的执行顺序是根据文件名的字典序确定,从小到大。
验证:把 a.go 文件名改为 d.go,内容不变。运行结果如下,先执行 b.go、c.go 的 init() 函数,然后才执行 d.go 的init() 函数。结论正确。
3、多个包中多个 init() 函数(不存在依赖)
有a、b、c、main 4个包,每个包有1个同名源文件 a.go、b.go、c.go、main.go。
// a 包
// a.go
package a
func init() {
println("a init function called")
}
// b 包
// b.go
package b
func init() {
println("b init function called")
}
// c 包
// c.go
package c
func init() {
println("c init function called")
}
// main 包
// main.go
package main
import (
_ "myGoProject/init_demo/a"
_ "myGoProject/init_demo/b"
_ "myGoProject/init_demo/c"
)
func init() {
println("init function called")
}
func main() {
println("main function called")
}
运行结果:
结论:多个包中多个 init() 函数,包不相互依赖,多个 init() 函数的执行顺序是根据 main 包中导入顺序确定,从上到下,最后再执行 main 包的 init 函数。
验证:把导入顺序改为b、c、a,其他代码不变。运行结果如下,先执行 b、c 包的 init() 函数,然后才执行 a 包的init() 函数,最后执行 main 包的 init 函数。结论正确。
import (
_ "myGoProject/init_demo/b"
_ "myGoProject/init_demo/c"
_ "myGoProject/init_demo/a"
)
4、多个包中多个 init() 函数(存在依赖)
有a、b、c、main 4 个包,每个包有1个同名源文件 a.go、b.go、c.go、main.go,main 包依赖 a 包,a 包依赖 b 包,b 包依赖 c 包,依赖关系为 main > a > b > c。
// a 包
// a.go
package a
import _ "myGoProject/init_demo/b"
func init() {
println("a init function called")
}
// b 包
// b.go
package b
import _ "myGoProject/init_demo/c"
func init() {
println("b init function called")
}
// c 包
// c.go
package c
func init() {
println("c init function called")
}
// main 包
// main.go
package main
import (
_ "myGoProject/init_demo/a"
)
func init() {
println("init function called")
}
func main() {
println("main function called")
}
运行结果:
结论:多个包中多个 init() 函数,包存在依赖,多个 init() 函数的执行顺序是根据包导入的依赖关系确定,从里到外,执行顺序为最后被依赖的包(最深层的包:c 包)最先被初始化,如依赖关系 main > a > b > c,则 init 函数执行顺序为 c > b > a > main。
验证:把依赖关系改为 main > b > a > c,其他代码不变。运行结果如下,c 包的 init() 函数先执行,接着执行 a 包的init() 函数,然后执行 b 包的 init 函数,最后执行 main 包的 init 函数。结论正确。
// a 包
// a.go
package a
import _ "myGoProject/init_demo/c"
func init() {
println("a init function called")
}
// b 包
// b.go
package b
import _ "myGoProject/init_demo/a"
func init() {
println("b init function called")
}
// c 包
// c.go
package c
func init() {
println("c init function called")
}
// main 包
// main.go
package main
import (
_ "myGoProject/init_demo/b"
)
func init() {
println("init function called")
}
func main() {
println("main function called")
}
5、一个包会被多个包同时导入,它只会被导入一次,init() 函数只执行一次
main 包依赖 a、c 包,a 包依赖 b、c 包,b 包依赖 c 包,c 包被 main、a、b 包依赖。
// a 包
// a.go
package a
import (
_ "myGoProject/init_demo/b"
_ "myGoProject/init_demo/c"
)
func init() {
println("a init function called")
}
// b 包
// b.go
package b
import _ "myGoProject/init_demo/c"
func init() {
println("b init function called")
}
// c 包
// c.go
package c
func init() {
println("c init function called")
}
// main 包
// main.go
package main
import (
_ "myGoProject/init_demo/a"
_ "myGoProject/init_demo/c"
)
func init() {
println("init function called")
}
func main() {
println("main function called")
}
运行结果:
五、小结
在 golang 中, init() 函数是在main() 函数之前执行的。全局常量、变量是在 init 函数之前初始化的。Go语言代码执行顺序为:import –> const –> var –>init()–>main()。
多个init() 函数执行顺序:
1、一个源文件中多个 init() 函数的执行顺序是根据定义顺序确定,从上到下。
2、 一个包中多个 init() 函数的执行顺序是根据文件名的字典序确定,从小到大。
3、多个包中多个 init() 函数,包不相互依赖,多个 init() 函数的执行顺序是根据 main 包中导入顺序确定,从上到下,最后再执行 main 包的 init 函数。
4、多个包中多个 init() 函数,包存在依赖,多个 init() 函数的执行顺序是根据包导入的依赖关系确定,从里到外,执行顺序为最后被依赖的包(最深层的包:c 包)最先被初始化,如依赖关系 main > a > b > c,则 init 函数执行顺序为 c > b > a > main。
5、一个包会被多个包同时导入,它只会被导入一次,init() 函数只执行一次。
在编码时避免依赖 init() 函数的执行顺序。虽然 init() 顺序是明确的,但代码可以更改,init() 函数之间的关系可能会使代码变得脆弱和容易出错,因此在编码时避免依赖 init() 函数的执行顺序。