Go教程:25 Go初始化init函数
1. 什么是Go语言init函数
在Go中,预定义init()函数会触发执行init函数中的代码,使其在包的任何其他代码之前运行. 该代码将在import package后立即执行,并且可以在需要您的app以特定方式初始化, 例如,当您具有启动app的config或resource时. import package 使用init函数的副作用来设置和初始化一些包的状态. 这通常用于register一个包和另一个包,以确保程序正在考虑代码的正确性.
尽管这init()是一个有用的工具,但有时会使代码难以阅读,因为难以搞清楚的所有包中的init()函数的执行. 因此,对于刚接触Go的开发人员来说,了解此功能的各个方面非常重要, 以便他们在coding时可以确保init()以清晰易懂的方式执行.
1.1 init函数的主要作用
- 初始化不能采用初始化表达式初始化的变量. eg: 在init函数初始化rand函数的时间种子,初始化一个随机32长度app_secret字符串
- 程序运行前的注册.在gorm 中有这样一段带代码
import _ "github.com/jinzhu/gorm/dialects/sqlite"
,相当于注册driver - 实现sync.Once功能.如果您要创出单例,在init中初始化是一个很好的方式.
import _ "github.com/jinzhu/gorm/dialects/sqlite"
这种方式使用go get/mod 下载其他package
1.2 init函数的主要特点
- init函数先于main函数自动执行,不能被其他函数调用;
- init函数没有输入参数,返回值;
- 每个包可以有多个init函数;
- 包的每个源文件也可以有多个init函数,这点比较特殊;
- 同一个包的init执行顺序,golang没有明确定义,编程时要注意程序不要依赖这个执行顺序.
- 不同包的init函数按照包导入的依赖关系决定执行顺序.
2. init函数在go语言中执行的顺序
为了使用导入的包,首先必须将其初始化. 初始化总是以单线程执行,并且按照包的依赖关系顺序执行.这通过Golang的运行时系统控制. 正如上图所示:
- 初始化导入的包(递归导入)
- 对包块中声明的变量进行计算和分配初始值
- 执行包中的init函数
main.go
package main
import "fmt"
var _ int64=s()
func init(){
fmt.Println("开始执行init函数")
}
func s() int64{
fmt.Println("开始初始化const/var")
return 1
}
func main(){
fmt.Println("开始执行main函数")
}
执行go run main.go
代码输出结果
$ go run play.go
开始初始化const/var
开始执行init函数
开始执行main函数
即使包被导入多次,初始化只需要一次.如果是多层级的import package 安装上面图示顺序执行.
3. init函数的Side Effects副作用
在Go中,有时希望导入软件包不是出于其内容,而是出于导入软件包时发生的副作用(Side Effects). 这通常意味着init()在导入的代码中有一条语句在其他任何代码之前执行, 从而使开发人员可以操纵其程序启动时的状态.这种技术被称为导入副作用(Side Effects).
导入副作用的一个常见用例是在代码中注册功能, 这使程序包知道程序需要使用代码的哪一部分. 在image封装中,例如,该image.Decode功能需要知道它正试图解码(其图像的格式jpg,png,gif等), 然后才能执行.您可以通过首先导入具有init()语句副作用(Side Effects)的特定程序来完成此操作.
假设您正尝试对一个.png文件执行image.Decode.代码段如下:
...
func decode(reader io.Reader) image.Rectangle {
m, _, err := image.Decode(reader)
if err != nil {
log.Fatal(err)
}
return m.Bounds()
}
...
上面这对代码会被编译出来,不会报错.但是当我们decode png文件的时候程序将会报错.
要修复,我们需要先为image.Decode注册图片格式png.非常幸运的是image/png包init函数含一下声明.
image/png/reader.go
func init() {
image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
}
因此,如果我们import “image/png” 到我们上面的解码代码中,然后在image/png中的 image.RegisterFormat()
函数将最先执行.
最终代码如下:
...
import _ "image/png"
...
func decode(reader io.Reader) image.Rectangle {
m, _, err := image.Decode(reader)
if err != nil {
log.Fatal(err)
}
return m.Bounds()
}
这将设置状态并注册我们需要的png版本image.Decode(). 该注册将作为导入image/png包的副作用而执行.
您可能之前已经注意到空白标识符_(下划线)”image/png”. 这是必需的,因为Go不允许您导入程序中未使用的程序包. 通过包括空白标识符,导入本身的值将被丢弃,从而仅导入的副作用得以解决. 这意味着,即使我们从不image/png在代码中调用该包,也可以出于副作用而将其导入.
了解何时需要导入软件包的副作用(import _ “your/package”)非常重要. 如果没有正确注册,则程序可能会编译成功,但在运行时无法正常运行. 标准库中的软件包将在其文档中声明需要这种类型的导入. 如果编写的程序包需要导入以产生副作用, 则还应确保记录了init()正在使用的语句,以便导入程序包的用户将能够正确使用它.