这是我参与「第五届青训营」笔记创作活动的第 4 天
闭包
Go语言中闭包是引用了自由变量的函数,被引用的自由变量和函数一同存在,即使已经离开了自由变量的环境也不会被释放或者删除,在闭包中可以继续使用这个自由变量。闭包也可以说是一个函数和与其相关的引用环境组合而成的实体。简单来说,闭包=函数+引用环境。如下图所示就是闭包的示意图
闭包中的“延迟求值”
遇到的问题
先来看看下面这段代码
func test() []func() {
var s []func()//定义一个切片s,s的类型为func()
for i := 0; i < 2; i++ {
s = append(s, func() {
fmt.Println(&i, i)
})
}
return s
}
func main() {
for _, value := range test() {//遍历test()的返回值
value()
}
}
运行结果
问题解析
对于以上的运行结果不知道有没有在大家的意料之中。反正我刚开始是怎么也理解不了运行结果为什么输出的是i=2和i的地址。
其实很简单,for循环复用局部变量i,那么每次添加的匿名函数引用的自然是同一变量。添加操作仅仅是将匿名函数放入列表,并未执行。因此,当main执行这些函数时,它们读取的是环境变量i最后一次循环时的值(i==2)。
如何避免闭包中的“延迟求值”
解决方法就是每次用不同的环境变量或传参复制,让各自闭包环境各不相同。代码如下所示
package main
func test() []func() {
var s []func()
for i := 0; i < 2; i++ {
//go语言里面赋值就会发生拷贝,虽然都是x,但是每个x的地址都不同,x的地址里面的值也不同
x := i
s = append(s, func() {
println(&x, x)
})
}
return s
}
func main() {
result := test()
for _, value := range result {
value()
}
}
结果如下所示:
其它
多个匿名函数引用同一环境变量,由于闭包,导致每个匿名函数在不同时间读取到的环境变量会不一样,会让事情变得更加复杂。任何的修改行为都会影响其他函数取值,在并发模式下可能需要做同步处理。除此之外,对于性能要求较高的场合,也需要谨慎使用闭包。