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
相关推荐
梦想很大很大5 小时前
使用 Go + Gin + Fx 构建工程化后端服务模板(gin-app 实践)
前端·后端·go
lekami_兰10 小时前
MySQL 长事务:藏在业务里的性能 “隐形杀手”
数据库·mysql·go·长事务
却尘13 小时前
一篇小白也能看懂的 Go 字符串拼接 & Builder & cap 全家桶
后端·go
ん贤14 小时前
一次批量删除引发的死锁,最终我选择不加锁
数据库·安全·go·死锁
mtngt111 天前
AI DDD重构实践
go
Grassto3 天前
12 go.sum 是如何保证依赖安全的?校验机制源码解析
安全·golang·go·哈希算法·go module
Grassto4 天前
11 Go Module 缓存机制详解
开发语言·缓存·golang·go·go module
程序设计实验室5 天前
2025年的最后一天,分享我使用go语言开发的电子书转换工具网站
go
我的golang之路果然有问题5 天前
使用 Hugo + GitHub Pages + PaperMod 主题 + Obsidian 搭建开发博客
golang·go·github·博客·个人开发·个人博客·hugo
啊汉7 天前
古文观芷App搜索方案深度解析:打造极致性能的古文搜索引擎
go·软件随想