小艾的自留地

Stay foolish, Stay hungry

Go 程序由一系列 Go 包组成,代码的执行是在各个包之间来回跳转。
和其他语言一样,Go 也拥有自己的用户层入口——main 函数,通过 main 入口函数,逐步了解 Go 程序的执行次序。

main 函数

Go 语言中有一个特殊的函数:main 包中的 main 函数,也就是 main.main,它是所有 Go 可执行程序的用户层执行逻辑的入口函数。

Go 程序在用户层面的执行逻辑,会在这个函数内按照它的调用顺序展开。

main 函数的函数原型是这样的:

1
2
3
4
5
6
package main

func main() {
// 用户层执行逻辑
// ... ...
}

main 函数的函数原型非常简单,没有参数也没有返回值。

而且,Go 语言要求:可执行程序的 main 包必须定义 main 函数,否则 Go 编译器会报错——“runtime.main_main·f: function main is undeclared in the main package”。

不过需要注意的是,并不是只有 main 包中才允许有 main 函数,其他包中也是可以拥有名为 main 的函数或者方法,只是因为其可见性规则,非 main 包中自定义的 main 函数仅限于包内使用。

1
2
3
4
5
6
7
8
9
10
11
12
package pkg1

import "fmt"

func Main() {
main()
}

// 这里 main 函数就主要是用来在包 pkg1 内部使用的,它是没法在包外使用的
func main() {
fmt.Println("main func for pkg1")
}

init 函数

不过对于 main 包的 main 函数来说,还有一点需要明确,就是它虽然是用户层逻辑的入口函数,但它却不一定是用户层第一个被执行的函数

这是因为Go 语言还有一个特殊函数的存在——init 函数,它的作用是对包进行初始化

和 main.main 函数一样,init 函数也是一个无参数无返回值的函数:

1
2
3
4
5
6
package main

func init() {
// 包初始化逻辑
// ... ...
}

如果 main 包依赖的包中定义了 init 函数,或者是 main 包自身定义了 init 函数,那么 Go 程序在这个包初始化的时候,就会自动调用它的 init 函数,因此这些 init 函数的执行就都会发生在 main 函数之前。

注意:init 函数不用显式调用,否则会编译错误——“undefined: init”。

Go 包的初始化次序

从程序逻辑结构角度来看,Go 包是程序逻辑封装的基本单位,每个包可以理解成是一个“自治”的、封装良好的、对外部暴露有限接口的基本单元。

一个 Go 程序就是由一组包组成的,程序的初始化就是这些包的初始化。每个包拥有自己的依赖包、变量、常量、init 函数、(main 函数)等。

可以借助下面这张图来加深对初始化次序的理解。

首先,main 包依赖 pkg1、pkg4 这两个包,所以第一步,Go 会根据包的导入顺序,依次去初始化 main 包下面的依赖包。

第二步,Go 在进行包初始化的过程中,会采用深度优先的原则,递归初始化各个包的依赖包。
对应到上图,也就是 pkg1 依赖 pkg2,pkg2 依赖 pkg3,pkg3 没有依赖包,于是 Go 在 pkg3 包中按照常量 -> 变量 -> init 函数的顺序先对 pkg3 包进行初始化(这个就是深度优先原则,从内往外依次初始化)。

紧接着,在 pkg3 包初始化完毕后,Go 会回到 pkg2 包并对 pkg2 包进行初始化,接下来再回到 pkg1 包并对 pkg1 包进行初始化。在调用完 pkg1 包的 init 函数后,Go 就完成了 main 包的第一个依赖包 pkg1 的初始化。

接下来,Go 会初始化 main 包的第二个依赖包 pkg4,pkg4 包的初始化过程与 pkg1 包类似,也是先初始化它的依赖包 pkg5,然后再初始化自身。

然后,当 Go 初始化完 pkg4 包后也就完成了对 main 包所有依赖包的初始化,接下来初始化 main 包自身。

最后,在 main 包中,Go 同样会按照常量 -> 变量 -> init 函数的顺序进行初始化,执行完这些初始化工作后才正式进入程序的入口函数 main 函数。


对了,还有一点需要注意的是:如果一个包同时被多个包依赖,那么这个包仅会初始化一次

总结

关于 Go 包的初始化记住以下三点就行:

  • 依赖包按深度优先的次序进行初始化
  • 每个包内按以常量 -> 变量 -> init 函数的顺序进行初始化
  • 包内的多个 init 函数按出现次序进行自动调用

参考链接

评论