43

Go中的init函数

 5 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.
neoserver,ios ssh client

我们知道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


Recommend

  • 40
    • studygolang.com 6 years ago
    • Cache

    Go 中的 init 函数

    main 标识符是随处可见的,每一个 Go 程序都是从一个叫 main 的包中的 main 函数开始的,当 main 函数返回时,程序执行结束。 init 函数也扮演着特殊的角色,接下来我们将描述下 init 函数的属性并介绍下怎么使用它们。 init 函...

  • 69

    本文为译文,原文链接: https://spyhce.com/blog/understanding-new-and-init 本文的目的是讨论Python中 __new__ 和 __ini___ 的用法。 __new__ 和 _...

  • 36
    • studygolang.com 6 years ago
    • Cache

    init()函数

    init()函数优先于main()函数执行 每个源文件都可以包含一个init函数,这个init函数自动被go运行框架调用,执行的优先级最高。 让我们做一个代码来验证一下: ·目录结构: |-example2 |-initInfo...

  • 51
    • www.tuicool.com 6 years ago
    • Cache

    On the vagaries of init systems

    When I started working on Dinit I had only a fairly vague idea of the particulars of various other init systems, being familiar mainly with Sys V init and t...

  • 38
    • www.tuicool.com 6 years ago
    • Cache

    hg advent init

    This is the start of a daily series in the run-up to Christmas where I learn mercurial. This first post will be about what’s making me want to do this in the first place. I tried briefly a while back, but I didn’...

  • 58
    • www.tuicool.com 6 years ago
    • Cache

    Init Is Bad and You Should Feel Bad

    func init() in Go is a weird beast. It’s the only function you can have multiples of in the same package (yup, that’s right… give it a try). It gets run when the package is imported . And you shou...

  • 6

    main函数其实从之前的示例中我们已经发现,所有的例子都包含main函数,这也是Go语言中较为特殊的函数。执行的入口函数为main()不接受参数,也不返回参数不需要显示调用每个包都必须要包含单个main包...

  • 7

    在Go语言中,init()函数是一种特殊的函数,用于在程序启动时自动执行一次。它的存在为我们提供了一种机制,可以在程序启动时进行一些必要的初始化操作,为程序的正常运行做好准备。 在这篇文章中,我们将详细探讨init()函数的特点...

  • 11
    • zhangyiming748.github.io 1 year ago
    • Cache

    golang init 函数运行顺序

    golang init 函数运行顺序 2023-11-23 Golang ...

  • 8
    • zhangyiming748.github.io 1 year ago
    • Cache

    golang 中在init之前运行的函数

    golang 中在init之前运行的函数 2023-11-23 Golang ...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK