Go语言中的init函数

基本概念:

在Go语言中,init 函数是一个特殊的函数,它在程序执行开始之前自动执行。每个包可以包含一个或多个 init 函数,这些函数会在包被导入时自动执行。init 函数的主要用途包括:

  1. 初始化包级别的变量 :可以在 init 函数中对包级别的变量进行初始化。
  2. 执行一些初始化逻辑:例如,打开数据库连接、读取配置文件等。
  3. 注册服务或插件 :在某些情况下,init 函数可以用来注册服务或插件。

特点

  • 自动执行init 函数在包被导入时自动执行,不需要显式调用。
  • 无参数和返回值init 函数不能有任何参数,也不能有返回值。
  • 顺序执行 :在一个包中,所有的 init 函数会按照它们在文件中的出现顺序依次执行。如果一个包导入了其他包,那么被导入的包的 init 函数会先执行。

init 函数的执行顺序

1.文件结构简单,没有其他依赖的场景:

在Go语言中,一个文件中可以定义多个 init 函数。这些 init 函数的执行顺序遵循以下规则:

  1. 按代码顺序执行 :在一个文件中,多个 init 函数会按照它们在文件中出现的顺序依次执行。
  2. 文件间的顺序 :如果一个包中有多个文件,Go编译器会按照文件名的字母顺序加载这些文件,并依次执行每个文件中的 init 函数。

示例

假设有一个包 example,其中包含两个文件 a.gob.go,它们的代码如下:

a.go

go 复制代码
package example

import "fmt"

func init() {
    fmt.Println("init 1 in a.go")
}

func init() {
    fmt.Println("init 2 in a.go")
}

b.go

go 复制代码
package example

import "fmt"

func init() {
    fmt.Println("init 1 in b.go")
}

func init() {
    fmt.Println("init 2 in b.go")
}

main.go

go 复制代码
package main

import (
    "example"
    "fmt"
)

func main() {
    fmt.Println("main function")
}

执行顺序

  1. 文件加载顺序 :Go编译器会按照文件名的字母顺序加载文件,因此 a.go 会先于 b.go 被加载。
  2. 文件内 init 函数的执行顺序 :在每个文件中,init 函数会按照它们在文件中出现的顺序依次执行。

因此,程序的输出顺序将是:

css 复制代码
init 1 in a.go
init 2 in a.go
init 1 in b.go
init 2 in b.go
main function

这种设计确保了 init 函数的执行顺序是可预测的,开发者可以根据需要合理安排 init 函数的位置和顺序。

2.文件结构相对复杂,存在导入其他包的场景

init 函数的执行顺序不仅与当前包内的文件和代码顺序有关,还与包的导入关系有关。如果一个文件导入了其他包,并且这些被导入的包中也有 init 函数,那么执行顺序会更加复杂。

执行顺序规则

  1. 包的初始化顺序 :Go会按照包的依赖关系,从最底层的包开始初始化。如果包 A 导入了包 B,那么包 Binit 函数会先于包 Ainit 函数执行。
  2. 文件的加载顺序 :在同一个包内,文件按照文件名的字母顺序加载。每个文件中的 init 函数按照它们在文件中出现的顺序依次执行。
  3. 文件内 init 函数的执行顺序 :在同一个文件中,多个 init 函数按照它们在文件中出现的顺序依次执行。
  4. 导入包的 init 函数优先执行 :如果一个文件导入了其他包,那么被导入包的 init 函数会先于当前文件的 init 函数执行。

示例

假设有以下包和文件结构:

example

  • a.go:导入了包 sub1
  • b.go:导入了包 sub2

sub1

  • sub1.go:定义了一个 init 函数。

sub2

  • sub2.go:定义了一个 init 函数。

main.go

  • 导入了包 example

sub1/sub1.go

go 复制代码
package sub1

import "fmt"

func init() {
    fmt.Println("init in sub1")
}

sub2/sub2.go

go 复制代码
package sub2

import "fmt"

func init() {
    fmt.Println("init in sub2")
}

example/a.go

go 复制代码
package example

import (
    "example/sub1"
    "fmt"
)

func init() {
    fmt.Println("init 1 in a.go")
}

func init() {
    fmt.Println("init 2 in a.go")
}

example/b.go

go 复制代码
package example

import (
    "example/sub2"
    "fmt"
)

func init() {
    fmt.Println("init 1 in b.go")
}

func init() {
    fmt.Println("init 2 in b.go")
}

main.go

go 复制代码
package main

import (
    "example"
    "fmt"
)

func main() {
    fmt.Println("main function")
}

执行顺序分析

  1. 包的依赖关系
    • example 包依赖于 sub1sub2
    • sub1sub2 是最底层的包,它们的 init 函数会先执行。
  1. 文件的加载顺序
    • example 包中,文件 a.gob.go 按照文件名的字母顺序加载,因此 a.go 会先于 b.go 加载。
  1. init 函数的执行顺序
    • 首先执行 sub1 包的 init 函数。
    • 然后执行 sub2 包的 init 函数。
    • 接着执行 a.go 中的 init 函数(按顺序)。
    • 最后执行 b.go 中的 init 函数(按顺序)。

输出结果

程序的输出顺序将是:

csharp 复制代码
init in sub1
init in sub2
init 1 in a.go
init 2 in a.go
init 1 in b.go
init 2 in b.go
main function

从执行结果来看,同一个文件中的 init 函数在程序的生命周期中只会执行一次,无论这个文件被导入多少次。

Go 文件的初始化顺序

在Go语言中,文件的初始化顺序遵循一定的规则,以确保程序在启动时正确地初始化各个包和变量。

1. 包级别的初始化顺序

Go程序的初始化从main包开始,然后按照依赖关系递归地初始化其他包。具体来说,包的初始化顺序如下:

  1. 包级别的变量初始化
    • 在同一个包内,所有包级别的变量(即在函数外部声明的变量)按照它们在文件中出现的顺序进行初始化。
    • 如果变量的初始化表达式依赖于其他变量,那么这些变量会先被初始化。
  1. init函数
    • 每个包可以包含零个或多个init函数。init函数在包级别的变量初始化之后自动执行。
    • 同一个包内的init函数按照它们在文件中出现的顺序执行。
    • 不同文件中的init函数按照文件名的字母顺序执行。

2. 包之间的初始化顺序

Go程序的初始化是按照包的依赖关系进行的。具体来说:

  1. 依赖关系
    • 如果包A依赖于包B,那么包B会在包A之前被初始化。
    • 这种依赖关系是通过导入语句(import)来确定的。
  1. 递归初始化
    • Go编译器会根据导入关系构建一个依赖图,并按照拓扑排序的顺序来初始化各个包。
    • 这意味着所有被依赖的包会在依赖它们的包之前被初始化。

3. main包的初始化

main包是Go程序的入口点,它的初始化顺序如下:

  1. 包级别的变量初始化
    • main包中的所有包级别变量按照它们在文件中出现的顺序进行初始化。
  1. init函数
    • main包中的init函数在包级别变量初始化之后执行。
  1. main函数
    • 最后,main函数在所有init函数执行完毕后被调用,程序开始执行。

4. 示例

假设有以下文件结构:

css 复制代码
main.go
pkgA/a.go
pkgB/b.go

main.go内容如下:

go 复制代码
package main

import (
    "example/pkgA"
    "example/pkgB"
)

var mainVar = 10

func init() {
    fmt.Println("main init")
}

func main() {
    fmt.Println("main function")
}

pkgA/a.go内容如下:

go 复制代码
package pkgA

import "fmt"

var aVar = 20

func init() {
    fmt.Println("pkgA init")
}

pkgB/b.go内容如下:

go 复制代码
package pkgB

import "fmt"

var bVar = 30

func init() {
    fmt.Println("pkgB init")
}

假设main.go中导入了pkgApkgB,那么初始化顺序如下:

  1. 初始化pkgA包中的aVar
  2. 执行pkgA包中的init函数。
  3. 初始化pkgB包中的bVar
  4. 执行pkgB包中的init函数。
  5. 初始化main包中的mainVar
  6. 执行main包中的init函数。
  7. 调用main函数。

输出结果可能是:

csharp 复制代码
pkgA init
pkgB init
main init
main function
相关推荐
烛阴1 天前
Go 语言进阶:打造可复用的模块,导出你的专属包
后端·go
gopher_looklook3 天前
从零到一: 用Go语言搭建简易RPC框架并实践 (一)
后端·go
猫九森5 天前
go 循环处理无限极数据
数据结构·后端·go
川Princess5 天前
【后端开发】字节跳动青训营Cloudwego脚手架
go·字节跳动青训营·cwgo
Pandaconda7 天前
【Golang 面试题】每日 3 题(四十三)
开发语言·经验分享·笔记·后端·面试·golang·go
川Princess8 天前
【后端开发】字节跳动青训营之性能分析工具pprof
go·字节跳动青训营·bytedance
用户2237209117729 天前
Go微服务精讲:Go-Zero全流程实战即时通讯
go
嘿嘿9 天前
Grafana 快速搭建go-metrics 仪表盘备忘
后端·docker·go
烛阴10 天前
Go 语言进阶必学:&^ 操作符,高效清零的秘密武器!
后端·go
Pandaconda10 天前
【Golang 面试题】每日 3 题(四十一)
开发语言·经验分享·笔记·后端·面试·golang·go