乐闻世界logo
搜索文章和话题

When is the init function run on golang

7 个月前提问
3 个月前修改
浏览次数67

6个答案

1
2
3
4
5
6

init 函数在 Go 语言中具有特殊意义。它在每个包完成初始化后自动执行,但在任何其他函数被调用之前执行。具体来说,init 函数的执行时机如下:

  1. 当一个包被导入时,首先检查包是否已经导入且被初始化。如果还没有,先对该包的依赖进行初始化。
  2. 然后,在包级别的变量被初始化后,该包的init函数会被调用。这个过程是自动的,并且是在编译时就确定下来的。
  3. 如果一个包有多个init函数(可能分散在包的多个文件中),它们将按照它们在代码中出现的顺序被调用。
  4. 如果一个包被多个其他包导入,其init函数只会被执行一次。

这个机制保证了无论包被导入多少次,init函数只运行一次,并且在程序的主函数main运行之前。这样的设计用于执行临时的初始化任务,比如设置包内部的数据结构,初始化变量,或者是注册必要的信息。

例如,如果有一个数据库包,你可能会在init函数中设置数据库的连接池:

go
package database import "database/sql" var dbPool *sql.DB func init() { var err error dbPool, err = sql.Open("postgres", "connection_string") if err != nil { log.Fatalf("Database connection failed: %s", err) } } // 其他数据库操作函数...

在这个例子中,无论这个数据库包被导入多少次,或在程序的哪个地方被导入,init函数都会确保在程序执行任何数据库操作前数据库连接已经设置好。

2024年6月29日 12:07 回复

是的,假设你有这个

shell
var WhatIsThe = AnswerToLife() func AnswerToLife() int { // 1 return 42 } func init() { // 2 WhatIsThe = 0 } func main() { // 3 if WhatIsThe == 0 { fmt.Println("It's all a lie.") } }

AnswerToLife()保证在init()调用之前运行,并且init()保证在main()调用之前运行。

请记住,init()无论是否有 main ,它总是被调用,因此如果您导入具有函数的包init,它将被执行。

此外,每个包可以有多个init()函数;它们将按照它们在文件中显示的顺序执行(当然是在所有变量初始化之后)。如果它们跨越多个文件,它们将按词法文件名顺序执行(如@benc指出的):

函数似乎init()是按词法文件名顺序执行的。Go 规范表示“鼓励构建系统以词法文件名顺序向编译器呈现属于同一包的多个文件”。似乎是go build这样的。


许多内部 Go 包用于init()初始化表等,例如https://github.com/golang/go/blob/883bc6/src/compress/bzip2/bzip2.go#L480

2024年6月29日 12:07 回复

看这张图。:)

import --> const --> var --> init()

  1. 如果一个包导入了其他包,则首先初始化导入的包。

  2. 然后初始化当前包的常量。

  3. 然后初始化当前包的变量。

  4. 最后,init()调用当前包的函数。

一个包可以有多个 init 函数(在单个文件中或分布在多个文件中),并且按照它们呈现给编译器的顺序调用它们。

即使从多个包导入,一个包也只会初始化一次。

2024年6月29日 12:07 回复

对此需要添加一些内容(我会添加为评论,但在撰写本文时我还没有足够的声誉)

在同一个包中有多个 init,我还没有找到任何有保证的方法来知道它们的运行顺序。例如我有:

shell
package config - config.go - router.go

config.gorouter.go包含init()函数,但是当运行时router.go,函数首先运行(这导致我的应用程序出现恐慌)。

如果您有多个文件,每个文件都有自己的init()功能,请务必注意,不能保证您先获得一个文件。最好使用变量赋值,如OneToOne在他的示例中所示。最好的部分是:此变量声明将发生init()在包中的所有函数之前。

例如

配置.go:

shell
var ConfigSuccess = configureApplication() func init() { doSomething() } func configureApplication() bool { l4g.Info("Configuring application...") if valid := loadCommandLineFlags(); !valid { l4g.Critical("Failed to load Command Line Flags") return false } return true }

路由器.go:

shell
func init() { var ( rwd string tmp string ok bool ) if metapath, ok := Config["fs"]["metapath"].(string); ok { var err error Conn, err = services.NewConnection(metapath + "/metadata.db") if err != nil { panic(err) } } }

无论是否var ConfigSuccess = configureApplication()存在于router.go或中config.go,它都会在 EITHERinit()运行之前运行。

2024年6月29日 12:07 回复

这是另一个例子 - https://play.golang.org/p/9P-LmSkUMKY

shell
package main import ( "fmt" ) func callOut() int { fmt.Println("Outside is beinge executed") return 1 } var test = callOut() func init() { fmt.Println("Init3 is being executed") } func init() { fmt.Println("Init is being executed") } func init() { fmt.Println("Init2 is being executed") } func main() { fmt.Println("Do your thing !") }

上述程序的输出

shell
$ go run init/init.go Outside is being executed Init3 is being executed Init is being executed Init2 is being executed Do your thing !
2024年6月29日 12:07 回复

该函数什么时候init()运行?

使用 Go 1.16(2021 年第一季度),您将准确地看到它何时运行以及运行多长时间。

请参阅CL(更改列表)254659中的提交 7c58ef7,修复问题 41378

运行时:实现GODEBUG=inittrace=1支持

设置inittrace=1会导致运行时为每个带有 init 工作的包发出一行标准错误,总结执行时间和内存分配。

发出的函数调试信息init可用于查找 Go 启动性能的瓶颈或回归。

没有init功能的包(用户定义的或编译器生成的)被省略。

不支持跟踪插件 inits,因为它们可以同时执行。这将使跟踪的实现更加复杂,同时增加对非常罕见的用例的支持。可以通过测试显式导入插件包导入的主包来单独跟踪插件初始化。

shell
$ GODEBUG=inittrace=1 go test init internal/bytealg @0.008 ms, 0 ms clock, 0 bytes, 0 allocs init runtime @0.059 ms, 0.026 ms clock, 0 bytes, 0 allocs init math @0.19 ms, 0.001 ms clock, 0 bytes, 0 allocs init errors @0.22 ms, 0.004 ms clock, 0 bytes, 0 allocs init strconv @0.24 ms, 0.002 ms clock, 32 bytes, 2 allocs init sync @0.28 ms, 0.003 ms clock, 16 bytes, 1 allocs init unicode @0.44 ms, 0.11 ms clock, 23328 bytes, 24 allocs ...

受到Stapelberg@google.com的启发,他使用 GDB 构建了doInit 一个原型来测量init时间。

2024年6月29日 12:07 回复

你的答案