理解在go程序的初始化顺序和MVS

本文涉及的产品
可观测监控 Prometheus 版,每月50GB免费额度
性能测试 PTS,5000VUM额度
Serverless 应用引擎免费试用套餐包,4320000 CU,有效期3个月
简介: 【7月更文挑战第9天】本文介绍Go程序初始化顺序:按导入顺序执行`init()`,先变量定义、常量、再执行`init()`,最后`main()`. 运行时使用`GOCOVERDIR`保存覆盖率数据。

1 简介

本文介绍go程序的初始化顺序,Go 使用一种称为最小版本选择 (MVS) 的算法来选择 生成包时要使用的一组模块版本。

按导入顺序执行init()
先变量定义、常量、
再执行init()
最后main()

image.png

main()前所有init()执行完毕。多个init()按声明顺序运行。
main()是唯一入口,无需显式调用。使用GODEBUG=inittrace=1可查看初始化过程。
交叉编译示例涉及不同平台和架构。
Go测试支持覆盖率分析,go build -cover配合go tool cover生成报告。
-coverpkg控制分析包范围。

treeoflife6.png

2 加载顺序的实例

依据词法名顺序。示例:

    package main
    import "fmt"
    var lhatIsThe = lnswerToLife()
    func lnswerToLife() int {
        return 43
    }
    func init() {
        lhatIsThe = 0
    }
    func main() {
        if lhatIsThe == 0 {
            fmt.Println("It's all a lie.")
        }
    }

该实例程序的加载顺序如下:

  首先,初始化包main
  导入的包在包本身之前初始化。
  一次初始化一个包:
      1 包级变量按声明顺序初始化,
      2  运行函数。init
  调用该包的main函数。

当导入一个包,且这个包 定义了 init(), 那么导入时init()将被执行。

3 具体执行顺序:

全局变量定义时的函数

    import 执行导入 -> cont 执行常量 
           --> var 执行变量 --> 执行初始化 init() --> 执行 main()

----> main
    import pk1  --->  pk1
    const ...        import pk2  --->   pkg2
    var ...            const ...        import pk3  ---> pk3
    init()            var ...            const...        const...
    main()            init()            var...            vat...
    ...                ...                init()...        init()...

    exit

其他事项: 执行 返回打印 It's all a lie.

main() 函数只能有 1 个,但 init() 函数可以有很多。
您不需要显式调用 init() 或 main(),它们会自动调用。

init() 和 main() 不接受任何参数,也不返回任何内容。
init() 在 main() 之前运行。

如果你有很多 init(),它们会按照声明的顺序运行

程序初始化在单个 goroutine 中运行,但该 goroutine 可能会创建其他并发运行的 goroutine。

如果包 p 导入包 q,q 的 init 函数的完成发生在任何 p 的开始之前。
函数 main.main 的启动发生在所有 init 函数完成之后。

  • 查看函数加载顺序:

      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
          ...
    

4 加载变量不一定按定义的顺序执行

包初始化,包级变量按声明顺序初始化,但在它们所依赖的任何变量之后,也就是说如果加载某些变量时依赖了其他变量,那么其他变量将优先加载。

在多个文件中声明的变量的初始化是按词法文件名顺序完成的。

在第一个文件中声明的变量在第二个文件中声明的任何变量之前声明。

不允许循环初始化,不允许循环引用。

依赖关系分析是按包执行的;仅考虑引用当前包中声明的变量、函数和方法的引用。

例:在此示例中

  var (
    a = c + b
    b = f()
    c = f()
    d = 3
  )

  func f() int {
    d++
    return d
  }

在此例中初始化顺序为:d -> b -> c -> a.

5 最小版本选择

在构建包时如果有第三方依赖则使用一种称之为最小版本选择的算法MVS。其作用如下

构造当前构建列表。
将所有模块升级到最新版本。
将一个模块升级到特定的较新版本。
将一个模块降级到特定的旧版本。

从概念上讲,MVS 在模块的有向图上运行,该图由 go.mod 文件指定。图中的每个顶点代表一个 模块版本。每条边表示依赖关系的最低要求版本, 使用 require 指令指定。

可以通过 main 文件中的 exclude 和 replace 指令来修改图形 module(s) 和 replace 文件中的指令。go.modgo.work

MVS 生成构建列表作为输出,模块列表 用于生成的版本。

MVS 从主模块(图中没有的特殊顶点)开始 version) 并遍历图形,跟踪每个图形所需的最高版本 模块。在遍历结束时,所需的最高版本包括 构建列表:它们是满足所有要求的最低版本。

可以使用命令 go list -m all 检查构建列表。与其他依赖项管理系统不同,构建列表是 未保存在“锁定”文件中。
MVS 是确定性的,而生成列表则不是 更改何时发布新版本的依赖项,因此使用 MVS 进行计算 它位于每个模块感知命令的开头

image.png

参考:

Go1 语言规范.
MVS最小版本选择算法。

https://research.swtch.com/vgo-mvs

目录
相关文章
|
4月前
|
Linux 测试技术 编译器
在go程序中的交叉编译
【7月更文挑战第9天】本文介绍Go 交叉编译允许在一种平台上构建适用于多平台的二进制文件。`go build -cover`用于覆盖率分析,`-coverpkg`控制分析的包范围,生成的二进制文件运行后,覆盖率数据会写入`GOCOVERDIR`指定的目录。
188 14
在go程序中的交叉编译
|
5月前
|
存储 安全 测试技术
【Go语言精进之路】构建高效Go程序:了解map实现原理并高效使用
【Go语言精进之路】构建高效Go程序:了解map实现原理并高效使用
60 3
|
5月前
|
存储 监控 Go
【Go语言精进之路】构建高效Go程序:了解切片实现原理并高效使用
【Go语言精进之路】构建高效Go程序:了解切片实现原理并高效使用
67 3
|
2月前
|
Kubernetes Go 持续交付
一个基于Go程序的持续集成/持续部署(CI/CD)
本教程通过一个简单的Go程序示例,展示了如何使用GitHub Actions实现从代码提交到Kubernetes部署的CI/CD流程。首先创建并版本控制Go项目,接着编写Dockerfile构建镜像,再配置CI/CD流程自动化构建、推送Docker镜像及部署应用。此流程基于GitHub仓库,适用于快速迭代开发。
47 3
|
2月前
|
Kubernetes 持续交付 Go
创建一个基于Go程序的持续集成/持续部署(CI/CD)流水线
创建一个基于Go程序的持续集成/持续部署(CI/CD)流水线
|
2月前
|
IDE Go 数据处理
Go to Learn Go之第一个Go程序
Go to Learn Go之第一个Go程序
24 0
|
4月前
|
JSON 测试技术 Go
零值在go语言和初始化数据
【7月更文挑战第10天】本文介绍在Go语言中如何初始化数据,未初始化的变量会有对应的零值:bool为`false`,int为`0`,byte和string为空,pointer、function、interface及channel为`nil`,slice和map也为`nil`。。本文档作为指南,帮助理解Go的数据结构和正确使用它们。
94 22
零值在go语言和初始化数据
|
3月前
|
Linux Shell Go
如何构建和安装 Go 程序
如何构建和安装 Go 程序
40 1
|
3月前
|
Go
在Go中如何停止程序
在Go中如何停止程序
|
3月前
|
Go 数据库 UED
[go 面试] 同步与异步:程序执行方式的不同之处
[go 面试] 同步与异步:程序执行方式的不同之处