likes
comments
collection
share

理解go程序的加载顺序和MVS

作者站长头像
站长
· 阅读数 54

1 简介

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

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

2 加载顺序的实例

main()前所有init()执行完毕。多个init()按声明顺序运行。 main()是唯一入口,无需显式调用。使用GODEBUG=inittrace=1可查看初始化过程。

交叉编译示例涉及不同平台和架构。

Go测试支持覆盖率分析,go build -cover配合go tool cover生成报告。 -coverpkg控制分析包范围。

依据词法名顺序。示例:

	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 变量可不按定义的顺序加载

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

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

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

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

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

例:在此示例中变量a,b,c,d并不是按定义顺序加载的。

  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程序的加载顺序和MVS

可以使用命令 go list -m all 检查构建列表。与其他依赖项管理系统不同,构建列表是 未保存在“锁定”文件中。

MVS 是确定性的,而生成列表则不是 更改何时发布新版本的依赖项,因此使用 MVS 进行计算 它位于每个模块感知命令的开头

参考:

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

research.swtch.com/vgo-mvs

转载自:https://juejin.cn/post/7389092138901372939
评论
请登录