闭包

闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。
-- wikipedia

闭包包含两部分:

  1. 函数本身
  2. 函数所引用的环境

Go 里闭包的函数必须是匿名函数。

/**
闭包:是引用了自由变量的函数。这个被引用的自由变量和这个函数一同存在,即使已经离开了创建的环境
*/
func myFunc() func() int {
	foo := 0
	//myFunc 里面的匿名函数可以访问并且更新 myFunc 里面的变量,这个变量的生命周期因为匿名函数的存在而延长。
	return func() int {
		foo++
		return foo
	}
}

func main() {
	bar := myFunc()
	value_1 := bar()
	value_2 := bar()
	println(value_1)
	println(value_2)
}

运行结果:

D:GoWorkspacesrcpolarismain>go run closures.go
1
2

使用闭包实现斐波那契额

package main

/**
使用闭包实现斐波那契额
*/

func makeFibGen() func() int {
	f1 := 0
	f2 := 1
	return func() (fib int) {
		fib = f1
		f2, f1 = (f1 + f2), f2
		return fib
	}
}

func main() {
	gen := makeFibGen()
	for i := 0; i < 10; i++ {
		println(gen())
	}
}

Go 中匿名函数的实现

package main

/**
匿名函数的实现
*/
func myFunc1(message int) {
	println(message)
}

func main() {
	f := func(message int) {
		println(message)
	}
	f(0x100)
	myFunc1(0x100)
}

编译:

D:GoWorkspacesrcpolarismain>go build -gcflags "-N -l -m" -o test2
# _/D_/GoWorkspace/src/polaris/main
.	est2.go:11:7: func literal does not escape

然后我们通过 go 提供的反汇编工具,反编译我们刚刚生成的 test 文件。

D:GoWorkspacesrcpolarismain>go tool objdump -s "main.main" ./test2
TEXT main.main(SB) D:/GoWorkspace/src/polaris/main/test2.go
  test2.go:10           0x45ad00                65488b0c2528000000      MOVQ GS:0x28, CX
  test2.go:10           0x45ad09                488b8900000000          MOVQ 0(CX), CX
  test2.go:10           0x45ad10                483b6110                CMPQ 0x10(CX), SP
  test2.go:10           0x45ad14                7642                    JBE 0x45ad58
  test2.go:10           0x45ad16                4883ec18                SUBQ $0x18, SP
  test2.go:10           0x45ad1a                48896c2410              MOVQ BP, 0x10(SP)
  test2.go:10           0x45ad1f                488d6c2410              LEAQ 0x10(SP), BP
  test2.go:11           0x45ad24                488d159d000200          LEAQ go.func.*+77(SB), DX
  test2.go:11           0x45ad2b                4889542408              MOVQ DX, 0x8(SP)
  test2.go:14           0x45ad30                48c7042400010000        MOVQ $0x100, 0(SP)
  test2.go:14           0x45ad38                488b0589000200          MOVQ go.func.*+77(SB), AX
  test2.go:14           0x45ad3f                ffd0                    CALL AX
  test2.go:15           0x45ad41                48c7042400010000        MOVQ $0x100, 0(SP)
  test2.go:15           0x45ad49                e852ffffff              CALL main.myFunc1(SB)
  test2.go:16           0x45ad4e                488b6c2410              MOVQ 0x10(SP), BP
  test2.go:16           0x45ad53                4883c418                ADDQ $0x18, SP
  test2.go:16           0x45ad57                c3                      RET
  test2.go:10           0x45ad58                e81373ffff              CALL runtime.morestack_noctxt(SB)
  test2.go:10           0x45ad5d                eba1                    JMP main.main(SB)
  :-1                   0x45ad5f                cc                      INT $0x3

TEXT main.main.func1(SB) D:/GoWorkspace/src/polaris/main/test2.go
  test2.go:11           0x45ad60                65488b0c2528000000      MOVQ GS:0x28, CX
  test2.go:11           0x45ad69                488b8900000000          MOVQ 0(CX), CX
  test2.go:11           0x45ad70                483b6110                CMPQ 0x10(CX), SP
  test2.go:11           0x45ad74                7635                    JBE 0x45adab
  test2.go:11           0x45ad76                4883ec10                SUBQ $0x10, SP
  test2.go:11           0x45ad7a                48896c2408              MOVQ BP, 0x8(SP)
  test2.go:11           0x45ad7f                488d6c2408              LEAQ 0x8(SP), BP
  test2.go:12           0x45ad84                e84731fdff              CALL runtime.printlock(SB)
  test2.go:12           0x45ad89                488b442418              MOVQ 0x18(SP), AX
  test2.go:12           0x45ad8e                48890424                MOVQ AX, 0(SP)
  test2.go:12           0x45ad92                e80939fdff              CALL runtime.printint(SB)
  test2.go:12           0x45ad97                e8d433fdff              CALL runtime.printnl(SB)
  test2.go:12           0x45ad9c                e8bf31fdff              CALL runtime.printunlock(SB)
  test2.go:13           0x45ada1                488b6c2408              MOVQ 0x8(SP), BP
  test2.go:13           0x45ada6                4883c410                ADDQ $0x10, SP
  test2.go:13           0x45adaa                c3                      RET
  test2.go:11           0x45adab                e8c072ffff              CALL runtime.morestack_noctxt(SB)
  test2.go:11           0x45adb0                ebae                    JMP main.main.func1(SB)

一共有三次 CALL, 排除调最后那个 runtimeCALL ,剩下两次分别对应了匿名函数调用以及正常的函数调用。而两次的区别在于正常的函数是 CALL main.myFunc(SB) , 匿名函数的调用是 CALL BX 。这两种不同的调用方式意味着什么?我们可以通过 gdb 来动态的跟踪这段代码来具体分析一下。

注意事项

  1. 匿名函数作为返回对象性能上要比正常的函数性能要差。
  2. 闭包可能会导致变量逃逸到堆上来延长变量的生命周期,给 GC 带来压力。

参考:
http://sunisdown.me/closures-in-go.html
https://gobyexample.com/closures

原文地址:https://www.cnblogs.com/shix0909/p/13545336.html