Go 语言同文件的多个 init 函数调用顺序?

2019-04-02 09:46:29 +08:00
 xeaglex

Go 语言允许同文件定义多个 init 函数,那么它们是按照什么顺序被调用呢?

网络上大多数资料都说:

按照声明顺序进行调用(实测结果也是这样)

但也有少量帖子的说法是:

Go 语言没有明确定义这个顺序,因此不建议依赖其调用顺序进行编程(我也的确没找到官方文档提到这一点)

所以哪种说法比较正确?官方到底有没有明确定义呢?

5254 次点击
所在节点    程序员
21 条回复
mengzhuo
2019-04-02 09:52:57 +08:00
是函数在符号表里的字母顺序

特殊的会提前,比如说 internal/cpu
skiy
2019-04-02 10:08:15 +08:00
没试过,不会有冲突的吗?代码如何的?
xeaglex
2019-04-02 10:18:17 +08:00
可能我讲得不清楚,给个例子吧:

```go
// test.go

package main

import "fmt"

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

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

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

func main() {
}

```
xeaglex
2019-04-02 10:18:46 +08:00
@mengzhuo
@skiy

之前说得可能不清楚,我在三楼补充了一个例子
yuikns
2019-04-02 10:35:07 +08:00
https://golang.org/ref/spec#Package_initialization

> To ensure reproducible initialization behavior, build systems are encouraged to present multiple files belonging to the same package in lexical file name order to a compiler.

这段说明了 init 的调用。

简而言之,

强制:

1. 可以定义多个,即便在同文件
2. 同包不重复初始化
3. 在同一个 goroutine 依次执行所有 init functions

推荐:

构建系统按照字典序依次执行。

因此 init 乱序都没有违规,更何况同文件的 init。

因此你这个只是 ub,只是恰好按照你期望执行。

如果你真的需要有序。你可以一个 init 依次调用就行
xpfd
2019-04-02 10:40:34 +08:00
想不明白为什么要定义多个 init 函数然后把执行顺序交给系统,定义一个 init 然后依次调用其他的不挺好吗? 这样做有什么好处? 一个 c 程序员的疑惑
gstqc
2019-04-02 10:41:29 +08:00
这种不太明确的代码就不要写也不用管怎么实现的吧
就如#5 所说,用 func init 调用就行了
gstqc
2019-04-02 10:43:23 +08:00
这种多个 func init 感觉就是茴香豆的茴字的四种写法
whitehack
2019-04-02 10:43:54 +08:00
yuikns
2019-04-02 10:55:22 +08:00
@xpfd 我理解,它相当于是一个 package 级别的 loader。多线程程序到这儿可以简单同步一下。比 C 少一个全局的 lock 或者 static

go 一直致力于简化多线程模型。此处也不例外。
yuikns
2019-04-02 10:58:46 +08:00
@xpfd 不好意思眼残看错了....
polythene
2019-04-02 11:03:48 +08:00
看生成的汇编吧,是按定义的顺序执行的。

go tool compile -S test.go
xeaglex
2019-04-02 11:10:31 +08:00
@yuikns 我的看法和你一致,只是网上资料几乎全说会按照声明顺序执行,搞得我不自信了哈哈
xeaglex
2019-04-02 11:12:13 +08:00
@polythene 看汇编跟看运行结果基本没有区别,这都只能说明目前编译器的表现,不代表它将来一定会遵守这个规则呀(如果的确是个 ub,而不是我检索能力太弱的话)
xeaglex
2019-04-02 11:20:03 +08:00
@xpfd 比如,假如我们要利用 init 初始化很多包环境,可以利用多个 init 来分割多个类别的初始化,这样可以让代码更利于阅读一点。
BruceAuyeung
2019-04-02 11:42:59 +08:00
同文件中的 init 确实是按照声明顺序(从上到下)执行的,这个 spec 有明确定义
>A package with no imports is initialized by assigning initial values to all its package-level variables followed by calling all init functions in the order they appear in the source, possibly in multiple files, as presented to the compiler.

但是不同文件中的 init 的执行顺序则由编译器先收到先执行这个规则来定,问题是,规范并没有定义应该先送哪个文件,仅仅是鼓励按文件名称词法序(即字母序)。
reus
2019-04-02 12:39:56 +08:00
同一个文件从上到下

不同文件顺序不定

表达依赖顺序其实很容易啊,不需要 init

var a = func() bool {
// ...
return true
}()

var b = func(_ bool) {
// ...
}(a)

用全局变量实现,因为 b 的计算依赖 a,所以 a 必然先于 b 计算,这个是规范规定的,也符合直觉。编译器会帮你计算依赖,保证按照依赖的顺序做初始化。
xeaglex
2019-04-02 13:16:14 +08:00
@BruceAuyeung 还真有这一段,我瞎了
Wisho
2019-04-02 14:01:56 +08:00
@xeaglex
楼主可以看看这篇分析,marcoma.xyz/2019/01/21/go-package-init/
有多种 case 下的 example code 和理论分析
reus
2019-04-02 16:33:37 +08:00
@Wisho 初始化顺序在规范文档里说得清清楚楚: https://golang.org/ref/spec#Package_initialization

现在的实现,以及未来的实现,都会以这个为依据。写代码来试验,得到的结果很多时候都是“未定义行为”,例如这篇文章里提到的多个文件的顺序,只是建议编译器按照文件名顺序,没有说一定是,不按照这个顺序也是允许的。

这篇文章的结论只对当前编译器的实现有效,如果以后编译器改了,而你的代码依赖这种“未定义行为”,那你的程序就要出错。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/551096

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX