40

Go中的init函数

 4 years ago
source link: https://www.tuicool.com/articles/FJV3ma3
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

我们知道Go程序的入口是main函数,当main函数退出了,程序也就退出了。init函数在Go程序中也扮演着重要的角色。这篇文章将会介绍init函数的特性以及如何使用它们。

init函数的作用:

  • 变量初始化

  • 检查和修复程序状态

  • 运行前注册,例如decoder,parser的注册

  • 运行只需计算一次的模块,像sync.once的作用

  • 其他

包初始化

如果需要使用一个导入的包,首先要对这个包进行初始化,这一步在main函数执行之前,由runtime来完成,分以下步骤:

1.  初始化导入的包;

2. 初始化包作用域中的变量;

3. 执行包中的init函数。

如果某个包被导入了多次,也只会执行一次包的初始化。

初始化顺序

Go一个包中可以包含很多文件,那么变量的初始化顺序与各个包的init函数执行顺序又是怎样的呢?

首先,runtime的初始化依赖机制会启动,当初始化依赖机制计算完成后,就需要决定a.go和z.go中的变量谁先初始化,这取决于呈现给编译器的文件顺序,一般来说是按文件名的字典序,但是变量间或各个包间有依赖需要另外讨论。如果z.go先被传到build系统,那么z.go的变量初始化就比a.go先一步完成。

同一个包中,变量的初始化顺序是按文件名的字典序,但同时runtime也会解析变量间依赖关系,没有依赖的变量最先初始化,init函数的执行顺序也同理。

来看下面按文件名字典序初始化的例子:

sandbox.go     

package main

import "fmt"

var _ int64 = s()

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

func s() int64 {
    fmt.Println("calling s() in sandbox.go")
    return 1
}

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

a.go

package main

import "fmt"

var _ int64 = a()

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

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

z.go

package main

import "fmt"

var _ int64 = z()

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

func z() int64 {
    fmt.Println("calling z() in z.go")
    return 3
}

程序输出:

calling a() in a.go
calling s() in sandbox.go
calling z() in z.go
init in a.go
init in sandbox.go
init in z.go
main

下面是按依赖关系决定初始化顺序的例子。

pack.go

package pack

import (
   "fmt"
   "test_util" // 引入test_util包
)

var Pack int = 6               

func init() {
   a := test_util.Util
   fmt.Println("init pack ", a)
}

test_util.go

package test_util

import "fmt"

var Util int = 5

func init() {
   fmt.Println("init test_util")
}

main.go

package main

import (
   "fmt"
   "pack"
   "test_util"                
)

func main() {
   fmt.Println(pack.Pack)
   fmt.Println(test_util.Util)
}

输出:

init test_util
init pack  5
6
5

由于pack包的初始化依赖test_util,因此运行时会先初始化test_util包再初始化pack包;

init函数的特性

init函数不需要传入参数也没有返回值,而且init函数是不能被其他函数调用的。

package main

import "fmt"

func init() {
    fmt.Println("init")
}

func main() {
    init()
}

上面的代码会报编译错误: undefined: init

在一个文件中也可以有多个init函数,看下面代码。

sandbox.go

package main

import "fmt"

func init() {
    fmt.Println("init 1")
}

func init() {
    fmt.Println("init 2")
}

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

utils.go

package main

import "fmt"

func init() {
    fmt.Println("init 3")
}

输出:

init 1
init 2
init 3
main

init函数的也广泛用在标准库中,比如 mathbzip2image

最常用的是初始化 不能使用初始化表达式的变量 ,也就是不能在变量声明的时候初始化的变量,看以下例子。

var square [10]int

func init() {
    for i := 0; i < 10; i++ {
        square[i] = i * i
    }
}

只是为了执行init函数而导入包

我们经常会在开源代码中见到有些导入的包中前面加了个下划线”_“,这表示只是想执行包中的init函数。

import _ "image/png"

image/png 包里的init函数作用是向 image 包注册png图片的解码器,见src/image/png/reader.go

func init() {
	image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
}

总结

小心并且不要滥用init函数,因为对于复杂点的项目来说,init函数的执行顺序难以捉摸。

参考文献

1.《init functions in Go》 https://medium.com/golangspec/init-functions-in-go-eac191b3860a

2. 《五分钟理解golang的init函数》 https://zhuanlan.zhihu.com/p/34211611

3.《 When is the init() function run? 》https://stackoverflow.com/questions/24790175/when-is-the-init-function-run

感谢阅读,欢迎大家留言,分享,指正~

JZn2Afi.jpg!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK