神奇的 Go init 函数
前言
哈嘍,兄弟們,我是asong。今天與大家聊一聊Go語(yǔ)言中的神奇函數(shù)init,為什么叫他神奇函數(shù)呢?因?yàn)樵摵瘮?shù)可以在所有程序執(zhí)行開(kāi)始前被調(diào)用,并且每個(gè)包下可以有多個(gè)init函數(shù)。這個(gè)函數(shù)使用起來(lái)比較簡(jiǎn)單,但是你們知道他的執(zhí)行順序是怎樣的嘛?本文我們就一起來(lái)解密。
init函數(shù)的特性
先簡(jiǎn)單介紹一下init函數(shù)的基本特性:
init函數(shù)先于main函數(shù)自動(dòng)執(zhí)行
每個(gè)包中可以有多個(gè)init函數(shù),每個(gè)包中的源文件中也可以有多個(gè)init函數(shù)
init函數(shù)沒(méi)有輸入?yún)?shù)、返回值,也未聲明,所以無(wú)法引用
不同包的init函數(shù)按照包導(dǎo)入的依賴(lài)關(guān)系決定執(zhí)行順序
無(wú)論包被導(dǎo)入多少次,init函數(shù)只會(huì)被調(diào)用一次,也就是只執(zhí)行一次
init函數(shù)的執(zhí)行順序
我在剛學(xué)習(xí)init函數(shù)時(shí)就對(duì)他的執(zhí)行順序很好奇,在谷歌上搜了幾篇文章,他們都有一樣的圖:
下圖來(lái)源于網(wǎng)絡(luò):
截屏2021-06-05 上午9.55.15這張圖片很清晰的反應(yīng)了init函數(shù)的加載順序:
包加載優(yōu)先級(jí)排在第一位,先層層遞歸進(jìn)行包加載
每個(gè)包中加載順序?yàn)?#xff1a;const > var > init,首先進(jìn)行初始化的是常量,然后是變量,最后才是init函數(shù)。針對(duì)包級(jí)別的變量初始化順序,Go官方文檔給出這樣一個(gè)例子:
變量的初始化按出現(xiàn)的順序從前往后進(jìn)行,假若某個(gè)變量需要依賴(lài)其他變量,則被依賴(lài)的變量先初始化。所以這個(gè)例子中,初始化順序是 d -> b -> c -> a。
上圖只是表達(dá)了init函數(shù)大概的加載順序,有些細(xì)節(jié)我們還是不知道的,比如:當(dāng)前包下有多個(gè)init函數(shù),按照什么順序執(zhí)行,當(dāng)前源文件下有多個(gè)init函數(shù),這又按照什么順序執(zhí)行呢?本來(lái)想寫(xiě)個(gè)例子挨個(gè)驗(yàn)證一下的,后來(lái)一看Go官方文檔中都有說(shuō)明,也就沒(méi)有必要再寫(xiě)一個(gè)例子啦,直接說(shuō)結(jié)論吧:
如果當(dāng)前包下有多個(gè)init函數(shù),首先按照源文件名的字典序從前往后執(zhí)行。
若一個(gè)文件中出現(xiàn)多個(gè)init函數(shù),則按照出現(xiàn)順序從前往后執(zhí)行。
前面說(shuō)的有點(diǎn)亂,對(duì)init函數(shù)的加載順序做一個(gè)小結(jié):
從當(dāng)前包開(kāi)始,如果當(dāng)前包包含多個(gè)依賴(lài)包,則先初始化依賴(lài)包,層層遞歸初始化各個(gè)包,在每一個(gè)包中,按照源文件的字典序從前往后執(zhí)行,每一個(gè)源文件中,優(yōu)先初始化常量、變量,最后初始化init函數(shù),當(dāng)出現(xiàn)多個(gè)init函數(shù)時(shí),則按照順序從前往后依次執(zhí)行,每一個(gè)包完成加載后,遞歸返回,最后在初始化當(dāng)前包!
init函數(shù)的使用場(chǎng)景
還記得我之前的這篇文章嗎:go解鎖設(shè)計(jì)模式之單例模式,借用init函數(shù)的加載機(jī)制我們可以實(shí)現(xiàn)單例模式中的餓漢模式,具體怎么實(shí)現(xiàn)可以參考這篇文章,這里就不在寫(xiě)一遍了。
init函數(shù)的使用場(chǎng)景還是挺多的,比如進(jìn)行服務(wù)注冊(cè)、進(jìn)行數(shù)據(jù)庫(kù)或各種中間件的初始化連接等。Go的標(biāo)準(zhǔn)庫(kù)中也有許多地方使用到了init函數(shù),比如我們經(jīng)常使用的pprof工具,他就使用到了init函數(shù),在init函數(shù)里面進(jìn)行路由注冊(cè):
//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))) }這里就不擴(kuò)展太多了,更多標(biāo)準(zhǔn)庫(kù)中的使用方法大家可以自己去探索一下。
在這最后總結(jié)一下使用init要注意的問(wèn)題吧:
編程時(shí)不要依賴(lài)init的順序
一個(gè)源文件下可以有多個(gè)init函數(shù),代碼比較長(zhǎng)時(shí)可以考慮分多個(gè)init函數(shù)
復(fù)雜邏輯不建議使用init函數(shù),會(huì)增加代碼的復(fù)雜性,可讀性也會(huì)下降
在init函數(shù)中也可以啟動(dòng)goroutine,也就是在初始化的同時(shí)啟動(dòng)新的goroutine,這并不會(huì)影響初始化順序
init函數(shù)不應(yīng)該依賴(lài)任何在main函數(shù)里創(chuàng)建的變量,因?yàn)閕nit函數(shù)的執(zhí)行是在main函數(shù)之前的
init函數(shù)在代碼中不能被顯示的調(diào)用,不能被引用(賦值給函數(shù)變量),否則會(huì)出現(xiàn)編譯錯(cuò)誤。
導(dǎo)入包不要出現(xiàn)循環(huán)依賴(lài),這樣會(huì)導(dǎo)致程序編譯失敗
Go程序僅僅想要用一個(gè)package的init執(zhí)行,我們可以這樣使用:import _ "test_xxxx",導(dǎo)入包的時(shí)候加上下劃線(xiàn)就ok了
包級(jí)別的變量初始化、init函數(shù)執(zhí)行,這兩個(gè)操作都是在同一個(gè)goroutine中調(diào)用的,按順序調(diào)用,一次一個(gè)包
總結(jié)
好啦,這篇文章到這里就結(jié)束了,本身init函數(shù)就很好理解,寫(xiě)這篇文章的目的就是讓大家了解他的執(zhí)行順序,這樣在日常開(kāi)發(fā)中才不會(huì)寫(xiě)出bug。希望本文對(duì)大家有所幫助,我們下期見(jiàn)!
素質(zhì)三連(分享、點(diǎn)贊、在看)都是筆者持續(xù)創(chuàng)作更多優(yōu)質(zhì)內(nèi)容的動(dòng)力!我是asong,我們下期見(jiàn)。
總結(jié)
以上是生活随笔為你收集整理的神奇的 Go init 函数的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Go map[int64]int64 写
- 下一篇: 学会这几招让 Go 程序自己监控自己