入口函數與包初始化:搞清 Go 程序的執行次序
main.main 函數:Go 應用的入口函數
- Go 語言中有一個特殊的函數:main 包中的 main 函數,也就是 main.main,它是所有 Go 可執行程序的用戶層執行邏輯的入口函數。
- Go 程序在用戶層面的執行邏輯,會在這個函數內按照它的調用順序展開。
- main 函數的函數原型非常簡單,沒有參數也沒有返回值。
- Go 語言要求:可執行程序的 main 包必須定義 main 函數,否則 Go 編譯器會報錯。在啟動了多個 Goroutine 的 Go 應用中,main.main 函數將在 Go 應用的主 Goroutine 中執行。
- 不過對于 main 包的 main 函數來說,還需要明確一點,就是它雖然是用戶層邏輯的入口函數,但它卻不一定是用戶層第一個被執行的函數。
init 函數:Go 包的初始化函數
- Go 語言還有一個特殊函數,它就是用于進行包初始化的 init 函數了。和 main.main 函數一樣,init 函數也是一個無參數無返回值的函數。
- 如果 main 包依賴的包中定義了 init 函數,或者是 main 包自身定義了 init 函數,那么 Go 程序在這個包初始化的時候,就會自動調用它的 init 函數,因此這些 init 函數的執行就都會發生在 main 函數之前。
- 在初始化 Go 包時,Go 會按照一定的次序,逐一、順序地調用這個包的 init 函數。
- 一般來說,先傳遞給 Go 編譯器的源文件中的 init 函數,會先被執行;而同一個源文件中的多個 init 函數,會按聲明順序依次執行。
- 當我們要在 main.main 函數執行之前,執行一些函數或語句的時候,我們只需要將它放入 init 函數中就可以了。
Go 包的初始化次序
- Go 包是程序邏輯封裝的基本單元,每個包都可以理解為是一個“自治”的、封裝良好的、對外部暴露有限接口的基本單元。
- 一個 Go 程序就是由一組包組成的,程序的初始化就是這些包的初始化。
- 每個 Go 包還會有自己的依賴包、常量、變量、init 函數(其中 main 包有 main 函數)等。
- 我們在閱讀和理解代碼的時候,需要知道這些元素在在程序初始化過程中的初始化順序,這樣便于我們確定在某一行代碼處這些元素的當前狀態。
- Go 在進行包初始化的過程中,會采用“深度優先”的原則,遞歸初始化各個包的依賴包。
- Go 會按照“常量 -> 變量 -> init 函數”的順序進行初始化,執行完這些初始化工作后才正式進入程序的函數。包內的多個 init 函數按出現次序進行自動調用。
init 函數的用途
- init 函數的第一個常用用途:重置包級變量值。init 函數就好比 Go 包真正投入使用之前唯一的“質檢員”,負責對包內部以及暴露到外部的包級數據(主要是包級變量)的初始狀態進行檢查。
- init 函數的第二個常用用途,是實現對包級變量的復雜初始化。
- init 函數的第三個常用用途:在 init 函數中實現“注冊模式”。通過在 init 函數中注冊自己的實現的模式,就有效降低了 Go 包對外的直接暴露。