分享
  1. 首页
  2. 文章

Go中的init函数

deletelazy · · 934 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

来源: mp.weixin.qq.com/s/HptVvXho3...

欢迎关注公众号《Go后端干货》

各种Go,后端技术,面试题分享

正文

我们知道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函数的也广泛用在标准库中,比如math,bzip2,image

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

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》 medium.com/golangspec/...

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

3.《When is the init() function run?》stackoverflow.com/questions/2...

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


有疑问加站长微信联系(非本文作者)

本文来自:掘金

感谢作者:deletelazy

查看原文:Go中的init函数

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
934 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏