Go包中程式碼執行順序

go_9發表於2019-02-19

近來無事,研究了一下Go語言包中程式碼的執行順序,研究完之後別有一番收穫。

廢話不多說,直接上程式碼。

工程目錄結構

init
    src
        d
        d.go
        k.go
    a.go
    main.go
    z.go

其中main.go是入口檔案

貼出檔案中的程式碼

d.go

package d

import "fmt"

var _ int64 = D()

func init() {
    fmt.Println("init in d.go")
}

func D() int64 {
    fmt.Println("calling d() in a.go")
    return 2
}

k.go

package d

import "fmt"

var _ int64 = K()

func init() {
    fmt.Println("init in K.go")
}

func K() int64 {
    fmt.Println("calling K() in a.go")
    return 2
}

a.go

package main

import "fmt"

var _ int64 = a()

func init() {
    fmt.Println("init in a.go")
}

func a() int64 {
    fmt.Println("calling a() in a.go")
    return 2
}

main.go

package main

import (
    "fmt"
    "time"
    "d"
)

var _ int64 = s()

func init() {
    fmt.Println("init in sandbox.go")
}

func s() int64 {
    fmt.Println("calling s() in sandbox.go")
    return 2
}

func main() {
    d.D()
    fmt.Println("main")
    time.Sleep(10000*time.Second)
}

z.go

package main

import "fmt"

var _ int64 = z()

func init() {
    fmt.Println("init in z.go")
}

func z() int64 {
    fmt.Println("calling z() in z.go")
    return 2
}

那麼問題來了,當執行go build命令,控制檯能列印出什麼東西呢?順序又是怎麼的呢?

順序出來了。

  1. calling d() in a.go
  2. calling K() in a.go
  3. init in d.go
  4. init in K.go
  5. calling a() in a.go
  6. calling s() in sandbox.go
  7. calling z() in z.go
  8. init in a.go
  9. init in sandbox.go
  10. init in z.go
  11. calling d() in a.go
  12. main

如果你對Go語言基礎掌握的不是很好,看到這個結果是很不理解的。

下面給出解釋。

包的初始化

要想使用匯入的包首先需要初始化它,這是由golang的執行系統完成的,主要包括(順序很重要):

  • 初始化匯入的包(遞迴的定義)
  • 在包級別為宣告的變數計算並分配初始值
  • 執行包內的 init 函式(下面的空白識別符號就是一個例子)
  • 不管包被匯入多少次,都只會被初始化一次。

順序

Go 的包中有很多的檔案,如果變數和函式在包的多個檔案當中,那麼變數的初始化和 init 函式的呼叫順序又是什麼樣的呢?首先,初始化依賴機制會啟動(更多 Go 中的初始化依賴)當初始化依賴機制完成的時候,就需要決定 a.go 和 z.go 中的初始化變數誰會被更早的處理,而這要取決於呈現給編譯器的檔案順序。如果 z.go 先被傳遞到構建系統,那麼變數的初始化就會比在 a.go 中先一步完成,這也同樣適用於 init 函式的觸發。Go 語言規範建議我們始終使用相同的順序傳遞,即按照詞法順序傳遞包中的檔名。

為了保證可重複的初始化行為,構建系統鼓勵按照詞法檔名的順序將屬於同一個包中的多個檔案呈現給編譯器。

相關文章