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

本文涉及的产品
Serverless 应用引擎 SAE,800核*时 1600GiB*时
容器镜像服务 ACR,镜像仓库100个 不限时长
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 【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

目录
相关文章
|
1月前
|
Linux 测试技术 编译器
在go程序中的交叉编译
【7月更文挑战第9天】本文介绍Go 交叉编译允许在一种平台上构建适用于多平台的二进制文件。`go build -cover`用于覆盖率分析,`-coverpkg`控制分析的包范围,生成的二进制文件运行后,覆盖率数据会写入`GOCOVERDIR`指定的目录。
105 14
在go程序中的交叉编译
|
2月前
|
存储 安全 测试技术
【Go语言精进之路】构建高效Go程序:了解map实现原理并高效使用
【Go语言精进之路】构建高效Go程序:了解map实现原理并高效使用
39 3
|
2月前
|
存储 监控 Go
【Go语言精进之路】构建高效Go程序:了解切片实现原理并高效使用
【Go语言精进之路】构建高效Go程序:了解切片实现原理并高效使用
44 3
|
1月前
|
JSON 测试技术 Go
零值在go语言和初始化数据
【7月更文挑战第10天】本文介绍在Go语言中如何初始化数据,未初始化的变量会有对应的零值:bool为`false`,int为`0`,byte和string为空,pointer、function、interface及channel为`nil`,slice和map也为`nil`。。本文档作为指南,帮助理解Go的数据结构和正确使用它们。
80 22
零值在go语言和初始化数据
|
5天前
|
Linux Shell Go
如何构建和安装 Go 程序
如何构建和安装 Go 程序
13 1
|
11天前
|
Go
在Go中如何停止程序
在Go中如何停止程序
|
12天前
|
Go 数据库 UED
[go 面试] 同步与异步:程序执行方式的不同之处
[go 面试] 同步与异步:程序执行方式的不同之处
|
12天前
|
存储 缓存 Java
涨姿势啦!Go语言中正则表达式初始化的最佳实践
在Go语言中,正则表达式是处理字符串的强大工具,但其编译过程可能消耗较多性能。本文探讨了正则表达式编译的性能影响因素,包括解析、状态机构建及优化等步骤,并通过示例展示了编译的时间成本。为了优化性能,推荐使用预编译策略,如在包级别初始化正则表达式对象或通过`init`函数进行错误处理。此外,简化正则表达式和分段处理也是有效手段。根据初始化的复杂程度和错误处理需求,开发者可以选择最适合的方法,以提升程序效率与可维护性。
28 0
涨姿势啦!Go语言中正则表达式初始化的最佳实践
|
17天前
|
设计模式 Java 编译器
Go - 基于逃逸分析来提升程序性能
Go - 基于逃逸分析来提升程序性能
22 2
|
2月前
|
JSON 算法 测试技术
在go语言中调试程序
【6月更文挑战第29天】Go语言内置`testing`包支持单元测试、基准测试和模糊测试。`go test`命令可执行测试,如`-run`选择特定测试,`-bench`运行基准测试,`-fuzz`进行模糊测试。
46 2
在go语言中调试程序