说说 Go 中闭包的底层原理?
1. 什么是闭包?
一个函数内引用了外部的局部变量,这种现象,就称之为闭包。
例如下面的这段代码中,adder 函数返回了一个匿名函数,而该匿名函数中引用了 adder 函数中的局部变量 sum ,那这个函数就是一个闭包。
package main
import "fmt"
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
而这个闭包中引用的外部局部变量并不会随着 adder 函数的返回而被从栈上销毁。
我们尝试着调用这个函数,发现每一次调用,sum 的值都会保留在 闭包函数中以待使用。
func main() {
valueFunc:= adder()
fmt.Println(valueFunc(2)) // output: 2
fmt.Println(valueFunc(2)) // output: 4
}
闭包的底层原理
package main
import "fmt"
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main() {
valueFunc:= adder()
fmt.Println(valueFunc(2)) // output: 2
}
我们先对它进行逃逸分析,很容易发现 sum 作为 adder 函数局部变量,并不是分配在栈上,而是分配在堆上的。
这就解决了第一个疑惑:为什么 adder 函数返回后, sum 不会随之销毁?
$ go build -gcflags="-m -m -l" demo.go
# command-line-arguments
./demo.go:8:3: adder.func1 capturing by ref: sum (addr=true assign=true width=8)
./demo.go:7:9: func literal escapes to heap:
./demo.go:7:9: flow: ~r0 = &{storage for func literal}:
./demo.go:7:9: from func literal (spill) at ./demo.go:7:9
./demo.go:7:9: from return func literal (return) at ./demo.go:7:2
./demo.go:6:2: sum escapes to heap:
./demo.go:6:2: flow: {storage for func literal} = &sum:
./demo.go:6:2: from func literal (captured by a closure) at ./demo.go:7:9
./demo.go:6:2: from sum (reference) at ./demo.go:8:3
./demo.go:6:2: moved to heap: sum
./demo.go:7:9: func literal escapes to heap
./demo.go:15:23: valueFunc(2) escapes to heap:
./demo.go:15:23: flow: {storage for ... argument} = &{storage for valueFunc(2)}:
./demo.go:15:23: from valueFunc(2) (spill) at ./demo.go:15:23
./demo.go:15:23: flow: {heap} = {storage for ... argument}:
./demo.go:15:23: from ... argument (spill) at ./demo.go:15:13
./demo.go:15:23: from fmt.Println(valueFunc(2)) (call parameter) at ./demo.go:15:13
./demo.go:15:13: ... argument does not escape
./demo.go:15:23: valueFunc(2) escapes to heap
可另一个问题,又浮现出来了,就算它不会销毁,那闭包函数若是存储的若是 sum 拷贝后的值,那每次调用闭包函数,里面的 sum 应该都是一样的,调用两次都应该返回 2,而不是可以累加记录。
因此,可以大胆猜测,闭包函数的结构体里存储的是 sum 的指针。
为了验证这一猜想,只能上汇编了。
通过执行下面的命令,可以输出对应的汇编代码
go build -gcflags="-S" demo.go
```输出的内容相当之多,我提取出下面最关键的一行代码,它定义了闭包函数的结构体。
其中 F 是函数的指针,但这不是重点,重点是 sum 存储的确实是指针
type.noalg.struct { F uintptr; "".sum *int }(SB), CX
坚冰还盖着北海的时候,我看到了怒放的梅花。

浙公网安备 33010602011771号