Go:神奇的 init 函數

前言

今天與大家聊一聊Go語言中的神奇函數init,爲什麼叫他神奇函數呢?因爲該函數可以在所有程序執行開始前被調用,並且每個包下可以有多個init函數。這個函數使用起來比較簡單,但是你們知道他的執行順序是怎樣的嘛?本文我們就一起來解密。

init函數的特性

先簡單介紹一下init函數的基本特性:

init函數的執行順序

我在剛學習init函數時就對他的執行順序很好奇,在谷歌上搜了幾篇文章,他們都有一樣的圖:

下圖來源於網絡:

截屏 2021-06-05 上午 9.55.15

這張圖片很清晰的反應了init函數的加載順序:

var (
 a = c + b  // == 9
 b = f()    // == 4
 c = f()    // == 5
 d = 3      // == 5 after initialization has finished
)

func f() int {
 d++
 return d
}

變量的初始化按出現的順序從前往後進行,假若某個變量需要依賴其他變量,則被依賴的變量先初始化。所以這個例子中,初始化順序是 d -> b -> c -> a

上圖只是表達了init函數大概的加載順序,有些細節我們還是不知道的,比如:當前包下有多個init函數,按照什麼順序執行,當前源文件下有多個init函數,這又按照什麼順序執行呢?本來想寫個例子挨個驗證一下的,後來一看Go官方文檔中都有說明,也就沒有必要再寫一個例子啦,直接說結論吧:

前面說的有點亂,對init函數的加載順序做一個小結:

從當前包開始,如果當前包包含多個依賴包,則先初始化依賴包,層層遞歸初始化各個包,在每一個包中,按照源文件的字典序從前往後執行,每一個源文件中,優先初始化常量、變量,最後初始化init函數,當出現多個init函數時,則按照順序從前往後依次執行,每一個包完成加載後,遞歸返回,最後在初始化當前包!

init函數的使用場景

還記得我之前的這篇文章嗎:go 解鎖設計模式之單例模式,借用init函數的加載機制我們可以實現單例模式中的餓漢模式,具體怎麼實現可以參考這篇文章,這裏就不在寫一遍了。

init函數的使用場景還是挺多的,比如進行服務註冊、進行數據庫或各種中間件的初始化連接等。Go的標準庫中也有許多地方使用到了init函數,比如我們經常使用的pprof工具,他就使用到了init函數,在init函數里面進行路由註冊:

//go/1.15.7/libexec/src/cmd/trace/pprof.go
func init() {
 http.HandleFunc("/io", serveSVGProfile(pprofByGoroutine(computePprofIO)))
 http.HandleFunc("/block", serveSVGProfile(pprofByGoroutine(computePprofBlock)))
 http.HandleFunc("/syscall", serveSVGProfile(pprofByGoroutine(computePprofSyscall)))
 http.HandleFunc("/sched", serveSVGProfile(pprofByGoroutine(computePprofSched)))

 http.HandleFunc("/regionio", serveSVGProfile(pprofByRegion(computePprofIO)))
 http.HandleFunc("/regionblock", serveSVGProfile(pprofByRegion(computePprofBlock)))
 http.HandleFunc("/regionsyscall", serveSVGProfile(pprofByRegion(computePprofSyscall)))
 http.HandleFunc("/regionsched", serveSVGProfile(pprofByRegion(computePprofSched)))
}

這裏就不擴展太多了,更多標準庫中的使用方法大家可以自己去探索一下。

在這最後總結一下使用init要注意的問題吧:

總結

好啦,這篇文章到這裏就結束了,本身init函數就很好理解,寫這篇文章的目的就是讓大家瞭解他的執行順序,這樣在日常開發中纔不會寫出bug。希望本文對大家有所幫助,我們下期見!

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/xag2pzC8LDqhcjcXeNxVrA