Go中的init函數

來源: mp.weixin.qq.com/s/HptVvXho3…golang

歡迎關注公衆號《Go後端乾貨》面試

各類Go,後端技術,面試題分享後端

正文

  咱們知道Go程序的入口是main函數,當main函數退出了,程序也就退出了。init函數在Go程序中也扮演着重要的角色。這篇文章將會介紹init函數的特性以及如何使用它們。bash

init函數的做用:函數

  • 變量初始化
  • 檢查和修復程序狀態
  • 運行前註冊,例如decoder,parser的註冊
  • 運行只需計算一次的模塊,像sync.once的做用
  • 其餘

包初始化

  若是須要使用一個導入的包,首先要對這個包進行初始化,這一步在main函數執行以前,由runtime來完成,分如下步驟:ui

  1. 初始化導入的包;
  2. 初始化包做用域中的變量;
  3. 執行包中的init函數。

  若是某個包被導入了屢次,也只會執行一次包的初始化。spa

初始化順序

  Go一個包中能夠包含不少文件,那麼變量的初始化順序與各個包的init函數執行順序又是怎樣的呢?code

  首先,runtime的初始化依賴機制會啓動,當初始化依賴機制計算完成後,就須要決定a.go和z.go中的變量誰先初始化,這取決於呈現給編譯器的文件順序,通常來講是按文件名的字典序,可是變量間或各個包間有依賴須要另外討論。若是z.go先被傳到build系統,那麼z.go的變量初始化就比a.go先一步完成。orm

  同一個包中,變量的初始化順序是按文件名的字典序,但同時runtime也會解析變量間依賴關係,沒有依賴的變量最早初始化,init函數的執行順序也同理。cdn

  來看下面按文件名字典序初始化的例子:

sandbox.go

package main

import "fmt"

var _ int64 = s()

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

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

func main() {
    fmt.Println("main")
}
複製代碼

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
}
複製代碼

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 3
}
複製代碼

程序輸出:

calling a() in a.go
calling s() in sandbox.go
calling z() in z.go
init in a.go
init in sandbox.go
init in z.go
main
複製代碼

  下面是按依賴關係決定初始化順序的例子。

pack.go

package pack

import (
   "fmt"
   "test_util" // 引入test_util包
)

var Pack int = 6               

func init() {
   a := test_util.Util
   fmt.Println("init pack ", a)
}
複製代碼

test_util.go

package test_util

import "fmt"

var Util int = 5

func init() {
   fmt.Println("init test_util")
}
複製代碼

main.go

package main

import (
   "fmt"
   "pack"
   "test_util"                
)

func main() {
   fmt.Println(pack.Pack)
   fmt.Println(test_util.Util)
}
複製代碼

輸出:

init test_util
init pack  5
6
5
複製代碼

  因爲pack包的初始化依賴test_util,所以運行時會先初始化test_util包再初始化pack包;

init函數的特性

  init函數不須要傳入參數也沒有返回值,並且init函數是不能被其餘函數調用的。

package main

import "fmt"

func init() {
    fmt.Println("init")
}

func main() {
    init()
}
複製代碼

  上面的代碼會報編譯錯誤:undefined: init。

  在一個文件中也能夠有多個init函數,看下面代碼。

sandbox.go

package main

import "fmt"

func init() {
    fmt.Println("init 1")
}

func init() {
    fmt.Println("init 2")
}

func main() {
    fmt.Println("main")
}
複製代碼

utils.go

package main

import "fmt"

func init() {
    fmt.Println("init 3")
}
複製代碼
輸出:

init 1
init 2
init 3
main
複製代碼

  init函數的也普遍用在標準庫中,好比mathbzip2image

  最經常使用的是初始化不能使用初始化表達式的變量,也就是不能在變量聲明的時候初始化的變量,看如下例子。

var square [10]int

func init() {
    for i := 0; i < 10; i++ {
        square[i] = i * i
    }
}
複製代碼

只是爲了執行init函數而導入包

  咱們常常會在開源代碼中見到有些導入的包中前面加了個下劃線」_「,這表示只是想執行包中的init函數。

import _ "image/png"
複製代碼

  image/png包裏的init函數做用是向image包註冊png圖片的解碼器,見src/image/png/reader.go

func init() {
	image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
}
複製代碼

總結

  當心而且不要濫用init函數,由於對於複雜點的項目來講,init函數的執行順序難以捉摸。

參考文獻

1.《init functions in Go》 medium.com/golangspec/…

2.《五分鐘理解golang的init函數》zhuanlan.zhihu.com/p/34211611

3.《When is the init() function run?》stackoverflow.com/questions/2…

感謝閱讀,歡迎你們留言,分享,指正~

相關文章
相關標籤/搜索