Go语言闭包中的”延迟求值“ | 青训营笔记

113 阅读2分钟

这是我参与「第五届青训营」笔记创作活动的第 4 天

闭包

Go语言中闭包是引用了自由变量的函数,被引用的自由变量和函数一同存在,即使已经离开了自由变量的环境也不会被释放或者删除,在闭包中可以继续使用这个自由变量。闭包也可以说是一个函数和与其相关的引用环境组合而成的实体。简单来说,闭包=函数+引用环境。如下图所示就是闭包的示意图

image.png

闭包中的“延迟求值”

遇到的问题

先来看看下面这段代码

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()
   }
}

运行结果

image.png

问题解析

对于以上的运行结果不知道有没有在大家的意料之中。反正我刚开始是怎么也理解不了运行结果为什么输出的是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()
   }
}

结果如下所示:

image.png

其它

多个匿名函数引用同一环境变量,由于闭包,导致每个匿名函数在不同时间读取到的环境变量会不一样,会让事情变得更加复杂。任何的修改行为都会影响其他函数取值,在并发模式下可能需要做同步处理。除此之外,对于性能要求较高的场合,也需要谨慎使用闭包。