关于golang:14init你用对了吗

40次阅读

共计 2045 个字符,预计需要花费 6 分钟才能阅读完成。

本文视频地址

在 Go 语言中,通过包的 init 函数来实现初始化的工作。

1. init 函数

Go 语言中有两个非凡的函数:

1) main 包中的 main 函数,它是所有 Go 可执行程序的入口函数;2) 包级别的 init 函数。

init 函数是一个无参无返回值的函数:

func init() {...}

如果一个包定义了 init 函数,Go 运行时会负责在该包初始化时调用它的 init 函数。咱们不能显式调用 init,否则会在编译期间报错。

一个 Go 包能够领有多个 init 函数,每个组成包的源文件中亦能够定义多个 init 函数。在初始化该包时,Go 运行时会依照肯定的秩序逐个程序地调用该包的 init 函数。每个 init 函数在整个 Go 程序生命周期内仅会被执行一次。因而,init 函数能够做一些包级初始化以及包级数据初始状态的查看工作。
一般来说,先被传递给 Go 编译器的源文件中的 init 函数先被执行;同一个源文件中的多个 init 函数按申明程序顺次执行。在 Go 语言中:不要依赖 init 函数的执行秩序。

2. 程序初始化程序

除了 init 函数是程序执行并仅被执行一次之外,Go 程序初始化程序也给 init 函数提供了前提条件。

  • main 包依赖 pkg1、pkg4 两个包;
  • Go 运行时会依据包导入的程序,先去初始化 main 包的第一个依赖包 pkg1;
  • Go 运行时遵循“深度优先”准则查看到:pkg1 依赖 pkg2,于是 Go 运行时去初始化 pkg2;
  • pkg2 依赖 pkg3,Go 运行时去初始化 pkg3;
  • pkg3 没有依赖包,于是 Go 运行时在 pkg3 包中依照”常量 -> 变量 -> init 函数 ” 的程序进行初始化;
  • pkg3 初始化结束后,Go 运行时会回到 pkg2 并对 pkg2 进行初始化;接下来再回到 pkg1 并对 pkg1 进行初始化;
  • 在调用完 pkg1 的 init 函数后,Go 运行时实现 main 包的第一个依赖包 pkg1 的初始化;
  • Go 运行时接下来会初始化 main 包的第二个依赖包 pkg4;
  • pkg4 的初始化过程与 pkg1 相似,也是先初始化其依赖包 pkg5,而后再初始化本身;
  • 当 Go 运行时初始化完 pkg4 后,也就实现了对 main 包所有依赖包的初始化,接下来初始化 main 包本身;
  • 在 main 包中,Go 运行时会依照”常量 -> 变量 -> init 函数 ” 的程序进行初始化,执行完这些初始化工作后才正式进入程序的入口函数 main 函数。

init 函数适宜做包级数据初始化和初始状态查看的起因就是 init 函数执行在包的包级变量之后。

3. 应用 init 函数查看包级变量的初始状态

init 函数负责对包外部以及裸露到内部的包级数据(次要是包级变量)的初始状态进行查看。

a) 重置包级变量值
// $GOROOT/src/context/context.go
// closedchan is a reusable closed channel.
var closedchan = make(chan struct{})
func init() {close(closedchan)
}

context 包在 cancelCtx 的 cancel 办法中须要一个可复用的、处于敞开状态的 channel,于是 context 包定义了一个未导出包级变量 closedchan 并对其进行了初始化。

b) 对包级变量进行初始化,保障其后续可用
// $GOROOT/src/net/http/h2_bundle.go
var (
        http2VerboseLogs    bool
        http2logFrameWrites bool
        http2logFrameReads  bool
        http2inTests        bool
)

func init() {e := os.Getenv("GODEBUG")
        if strings.Contains(e, "http2debug=1") {http2VerboseLogs = true}
        if strings.Contains(e, "http2debug=2") {
                http2VerboseLogs = true
                http2logFrameWrites = true
                http2logFrameReads = true
        }
}
c) init 函数中的“注册模式”
// github.com/lib/pq/conn.go
...
func init() {sql.Register("postgres", &Driver{})
}
... 

导入 lib/pq 的副作用就是 Go 运行时会将 lib/pq 作为 main 包的依赖包,Go 运行时会初始化 pq 包,pq 包的 init 函数执行。在 pq 包的 init 函数中,pq 包将本人实现的 sql 驱动 (driver) 注册到 sql 包中。这样只有应用层代码在 Open 数据库的时候传入驱动的名字(这里是“postgres”),那么通过 sql.Open 函数返回的数据库实例句柄对数据库进行的操作实际上调用的都是 pq 这个驱动的相应实现。

d) init 函数中查看失败的解决办法

一旦 init 函数在查看包数据初始状态时遇到失败或谬误的状况(只管极少呈现),个别倡议间接调用 panic。或通过 log.Fatal 等办法记录异样日志后再调用 panic 使程序退出。

正文完
 0