【Golang】内置函数 - init()

Posted by 西维蜀黍 on 2021-02-25, Last Modified on 2021-10-02

init()

init() 函数的主要特点:

  • init() 函数先于 main函数自动执行,不能被其他函数调用;
  • init() 函数没有输入参数、返回值;
  • 一个包可以有多个 init() 函数,他们的执行顺序以每个文件作为单位,按照他们所在的文件的 lexical file name order
  • 一个包中的一个源文件可以有多个 init() 函数,这点比较特殊;
  • 同一个包的 init() 执行顺序,Golang没有明确定义,编程时要注意程序不要依赖这个执行顺序。
  • 不同包的 init() 函数按照包导入的依赖关系决定执行顺序。

Demo

变量初始化先于init()

package main

import (
	"fmt"
)

var swV int = privateF()

func privateF() int {
	fmt.Println("call a1's privateF")
	return 5
}

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

func main() {
	fmt.Println("sw_pprof test")
}

输出:

call a1's privateF
call init
sw_pprof test

A package with no imports is initialized by assigning initial values to all its package-level variables followed by calling all init functions

初始化顺序:在同一个package 内,变量初始化->init()(-> main() )。

多个变量

// sw_pprof.go
package main

import (
	"fmt"
)

var swV1 = privateF()
var swV2 = privateF2()

func privateF() int {
	fmt.Println("call main's privateF")
	return 5
}

func privateF2() int {
	fmt.Println("call main's privateF2")
	return 5
}

func init() {
	fmt.Println("call main's init")
}

func main() {}

输出:

call main's privateF
call main's privateF2
call main's init

Within a package, package-level variable initialization proceeds stepwise, with each step selecting the variable earliest in declaration order which has no dependencies on uninitialized variables.

More precisely, a package-level variable is considered ready for initialization if it is not yet initialized and either has no initialization expression or its initialization expression has no dependencies on uninitialized variables. Initialization proceeds by repeatedly initializing the next package-level variable that is earliest in declaration order and ready for initialization, until there are no variables ready for initialization.

For example, given the declarations

package main

import "fmt"

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

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

func main() {
	fmt.Printf("a: %v, b: %v, c: %v, d: %v", a, b, c, d)
}

the initialization order is d, b, c, a. Note that the order of subexpressions in initialization expressions is irrelevant: a = c + b and a = b + c result in the same initialization order in this example.

输出:

a: 9, b: 4, c: 5, d: 5

一个文件中有多个 init()

package main
import "fmt"
func init() {
   fmt.Println("init 1")
}
func init() {
   fmt.Println("init 2")
}
func main() {
   fmt.Println("main")
}

输出:

init 1
init 2
main

init() 函数比较特殊,可以在一个 package 的同一个文件内被多次定义,执行顺序为被定义的先后顺序从先后到(从上到下)。

一个 package 中有多个 init()

// a1.go
package a

import "fmt"

func init() {
	fmt.Println("call a1's init")
}

func A1F1() {
	fmt.Println("call a1's a1F")
}

// a2.go
package a

import "fmt"

func init() {
	fmt.Println("call a2's init")
}

// main.go
package main

import (
	"GoPlayGround/a"
)

func main() {
	a.A1F1()
}

输出:

call a1's init
call a2's init
call a1's a1F

当一个 package 中有多个 init() 时,会依次加载 package 中的各个文件,加载的顺序为 lexical file name order,当加载一个特定文件时,就会执行这个文件中定义的 init() 函数。

To ensure reproducible initialization behavior, build systems are encouraged to present multiple files belonging to the same package in lexical file name order to a compiler.

同一个 package 下多个源文件执行顺序

  1. 先初始化该包下的所有源文件中的变量
  2. 再执行该包下的所有源文件中的init(),执行顺序以文件作为单位,依次执行每个文件中的 init()(因为一个文件中可以定义多个 init(),执行顺序按照被定义的先后顺序),文件的执行顺序为 lexical file name order
// a1.go
package a

import "fmt"

var swV int = privateF()

func privateF() int {
	fmt.Println("call a1's privateF")
	return 5
}

func init() {
	fmt.Println("call a1's init")
}

func A1F1() {
	fmt.Println("call a1's a1F")
}
// a2.go
package a

import "fmt"

var swV2 int = privateF2()

func privateF2() int {
	fmt.Println("call a2's privateF2")
	return 5
}

func init() {
	fmt.Println("call a2's init")
}

func B1F1() {
	fmt.Println("call a2's a2F")
}
// main.go
package main

import (
	"GoPlayGround/sw_play/a"
	"fmt"
)

var swV int = privateF()

func privateF() int {
	fmt.Println("call main's privateF")
	return 5
}

func init() {
	fmt.Println("call main's init")
}

func main() {
	fmt.Println("sw_play test")
	a.A1F1()
}

不同包的加载顺序

根据依赖树进行加载,即不依赖任何包的包会先被加载。

$ tree
├── sw_pprof
│   ├── a
│   │   └── a1.go
│   ├── b
│   │   └── b1.go
│   └── sw_pprof.go
// sw_pprof.go
package main

import (
	"GoPlayGround/sw_pprof/a"
	"GoPlayGround/sw_pprof/b"
	"fmt"
)

var swV int = privateF()

func privateF() int {
	fmt.Println("call main's privateF")
	return 5
}

func init() {
	fmt.Println("call main's init")
}

func main() {
	fmt.Println("sw_pprof test")
	b.B1F1()
	a.A1F1()
}
// a1.go
package a

import "fmt"

var swV int = privateF()

func privateF() int {
	fmt.Println("call a1's privateF")
	return 5
}

func init() {
	fmt.Println("call a1's init1")
}

func init() {
	fmt.Println("call a1's init2")
}

func A1F1() {
	fmt.Println("call a1's a1F")
}
// b1.go
package b

import "fmt"

var swV int = privateF()

func privateF() int {
	fmt.Println("call b1's privateF")
	return 5
}

func init() {
	fmt.Println("call b1's init1")
}

func init() {
	fmt.Println("call b1's init2")
}

func B1F1() {
	fmt.Println("call b1's a1F")
}

输出:

call a1's privateF
call a2's privateF2
call a1's init
call a2's init
call b1's privateF
call b1's init1
call b1's init2
call main's privateF
call main's init
sw_pprof test
call b1's a1F
call a1's a1F

官方解释:

A package with no imports is initialized by assigning initial values to all its package-level variables followed by calling all init functions in the order they appear in the source, possibly in multiple files, as presented to the compiler.

个人理解:

  • If a package has imports, the imported packages are initialized before initializing the package itself. If multiple packages import a package, the imported package will be initialized only once.
  • 当然,这里也再次说明了”变量初始化先于init()“。而且,是把这个包中的声明的所有变量都初始化完成后,才会支持这个包下的 inti() ,执行顺序以文件作为单位,依次执行每个文件中的 init()(因为一个文件中可以定义多个 init(),执行顺序按照被定义的先后顺序),文件的执行顺序为 lexical file name order

init() 不能被直接调用

package main

import "fmt"

func init() {
	fmt.Println("init")
}
func main() {
	init() // 这里会报错: ./sw_play.go:9:2: undefined: init
}

导入不使用的包

import _ "net/http/pprof"

To import a package solely for its side-effects (initialization), use the blank identifier as explicit package name:

Golang对没有使用的导入包会编译报错,但是有时我们只想调用该包的 init() 函数,不使用包导出的变量或者方法,这时就采用上面的导入方案。

执行上述导入后,init() 函数会启动一个异步协程采集该进程实例的资源占用情况,并以 HTTP 服务接口方式提供给用户查询。

Reference