文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

一篇文章读懂Golang init函数执行顺序

2024-04-02 19:55

关注

1.init 函数简介

Golang init 函数是一种特殊的函数,主要用于完成程序的初始化工作,如初始化数据库的连接、载入本地配置文件、根据命令行参数初始化全局变量等。

package main

import "flag"

var gopath string

func init() {
	println("init a")
}

func init() {
	println("init b")
}

func init() {
	println("init c")
	// gopath may be overridden by --gopath flag on command line.
	flag.StringVar(&gopath, "gopath", "/root/go", "override default GOPATH")
}

func main() {
	println("main")
	flag.Parse()
	println(gopath)
}

运行输出:

$ go run main.go --gopath="/home/alice/go"
init a
init b
init c
main
/home/alice/go

之所以特殊,是因为 init 函数有如下特点:

2.执行顺序

既然一个程序可以有多个 init 函数,那么对于位于不同包、不同源文件中的多个 init 函数,其执行顺序是怎样的呢?

下面从多个方面去考察。

2.1 单个源文件的 init 执行顺序

package main

func init() {
	println("init a")
}

func init() {
	println("init b")
}

func init() {
	println("init c")
}

func main() {
	println("main")
}

运行输出:

$ go run main.go
init a
init b
init c
main

结论: 同一个源文件的 init 函数执行顺序与其定义顺序一致,从上到下。

2.2 单个包的 init 执行顺序

假设 main 包有三个源文件 a.go,b.go 和 c.go。

// a.go
package main

func init() {
	println("init a")
}

// b.go
package main

func init() {
	println("init b")
}

// c.go
package main

func init() {
	println("init c")
}

// main.go
package main

func init() {
	println("init main")
}

func main() {
	println("main")
}

编译运行输出:

$ go build && ./main
init a
init b
init c
init main
main

结论: 同一个包中不同源文件 init 函数的执行顺序,是根据文件名的字典序来确定。

2.3 main 包导入多个包时 init 执行顺序

2.3.1 不存在依赖

对于不同的包,如果不相互依赖的话,在 main 包中被 import,那么这种情况下,各个包的 init 执行顺序是怎样的呢?

假设有包 a,b 和 c,在 main.go 中被 import。

// a 包
// a.go
package a

func init() {
	println("init a")
}

// b 包
// b.go
package b

func init() {
	println("init b")
}

// c 包
// c.go
package c

func init() {
	println("init c")
}

// main 包
// main.go
package main

import (
	_ "main/a"
	_ "main/b"
	_ "main/c"
)

func init() {
	println("init main")
}

func main() {
	println("main")
}

编译运行输出:

$ go build && ./main
init a
init b
init c
init main
main

结论: 对于不同的包,如果不相互依赖的话,按照 main 包中导入顺序调用包的 init 函数,最后再调用 main 包的 init 函数。

2.3.2 存在依赖

对于不同的包,如果存在依赖关系的话,在 main 包中被 import,那么这种情况下,各个包的 init 执行顺序是怎样的呢?

假设有包 a,b 和 c,main 包 import a 包,a import b,b import c,即依赖关系为 main > a > b > c。

// a 包
// a.go
package a

import _ "main/b"

func init() {
	println("init a")
}

// b 包
// b.go
package b

import _ "main/c"

func init() {
	println("init b")
}

// c 包
// c.go
package c

func init() {
	println("init c")
}

// main 包
// main.go
package main

import (
	_ "main/a"
)

func init() {
	println("init main")
}

func main() {
	println("main")
}

编译运行输出:

$ go build && ./main
init c
init b
init a
init main
main

结论: 如果 package 存在依赖,不同包的 init 函数按照包导入的依赖关系决定执行顺序。 调用顺序为最后被依赖的最先被初始化,如导入顺序 main > a > b > c,则初始化顺序为 c > b > a > main,依次执行对应的 init 方法。

2.4 包级变量初始化与 init 函数执行顺序

如果包中存在包级变量,那么其初始化与 init 函数执行先后顺序如何呢?

还是假设有包 a,b 和 c,main 包 import a 包,a import b,b import c,即依赖关系为 main > a > b > c。且每个包都有一个包级变量,并通过函数完成其初始化。

// a 包
// a.go
package a

import _ "main/b"

var A = func() string {
	println("init var A")
	return "A"
}()

func init() {
	println("init a")
}

// b 包
// b.go
package b

import _ "main/c"

var B = func() string {
	println("init var B")
	return "B"
}()

func init() {
	println("init b")
}

// c 包
// c.go
package c

var C = func() string {
	println("init var C")
	return "C"
}()

func init() {
	println("init c")
}

// main 包
// main.go
package main

import (
	_ "main/a"
)

var m = func() string {
	println("init var m")
	return "m"
}()

func init() {
	println("init main")
}

func main() {
	println("main")
}

编译运行输出:

$ go build && ./main
init var C
init c
init var B
init b
init var A
init a
init var m
init main
main

结论: 可见每个包的包级变量初始化是在 init 函数执行之前完成的。不同包的 init 函数与包级变量的初始化顺序如下图所示。

3.小结

Golang 中的 init 是一种特殊的函数,主要用于完成程序的初始化工作。其特点有:

程序中如果在不同包的不同源文件有多个 init 函数时,其执行顺序可概述为:

程序的初始化和执行都起始于 main 包。如果 main 包还导入了其它的包,那么就会在编译时将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到 fmt 包,但它只会被导入一次,因为没有必要导入多次)。

当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行 init 函数,依次类推。

请务必铭记于心,虽然 init() 顺序是明确的,但代码可以更改,init() 函数之间的关系可能会使代码变得脆弱和容易出错,因此在编码时避免依赖 init() 函数的执行顺序。

参考文献

总结 

到此这篇关于一篇文章读懂Golang init函数执行顺序的文章就介绍到这了,更多相关Golang init函数执行顺序内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯