1、什么是闭包

闭包=函数+引用环境。当函数a()的内部函数b()被函数a()外的一个变量引用的时候,就创建了一个闭包。 闭包的作用就是在a()执行完并返回b()后,闭包使得垃圾回收机制GC不会收回a()所占用的资源,因为a()的内部函数b()的执行需要依赖a()中的变量i。

2、闭包例子

2.1 简单例子

试着想一想以下程序执行会输出什么

  1. func a() func() int {
  2. i := 0
  3. b := func() int {
  4. i++
  5. return i
  6. }
  7. return b
  8. }
  9. func main() {
  10. var f = a()
  11. fmt.Println(f())
  12. fmt.Println(f())
  13. }

答案是1、2
在main中 执行var f = a()函数a返回函数b,f引用了函数b的实例。执行fmt.Println(f()),执行函数b,而函数b内部依赖函数a的变量i,导致了GC不会回收函数a所占用的资源,变量i不在函数a的栈上,而是逃逸到堆上,所以在连续执行fmt.Println(f()),操作的都是同一个变量i
ps:使用命令go build -gcflags "-N -l -m" example/closure可以查看内存分配

这个呢?

  1. func a() func() int {
  2. i := 0
  3. b := func() int {
  4. i++
  5. return i
  6. }
  7. return b
  8. }
  9. func main() {
  10. fmt.Println(a()())
  11. fmt.Println(a()())
  12. }

答案是1、1
在main中执行a()()返回的函数直接调用,因此两次a()返回的不是同一个实例(即不是同一个闭包),不操作同一个i

2.2 循环闭包

以下程序会输出什么

  1. func main() {
  2. var f func()
  3. for i := 0; i < 3; i++ {
  4. f = func() {
  5. fmt.Println(i)
  6. }
  7. }
  8. f()
  9. }

本以为会输出2,但答案是3
匿名函数执行依赖for中的局部变量i,外界变量f引用匿名函数,因此形成了闭包,i逃逸到堆上,循环结束后i=3

  1. func main() {
  2. var funcs []func()
  3. for i := 0; i < 3; i++ {
  4. funcs = append(funcs, func() {
  5. fmt.Println(i)
  6. })
  7. }
  8. for j := 0; j < 3; j++ {
  9. funcs[j]()
  10. }
  11. }

输出3、3、3
原因跟上一个例子一样

那怎么解决呢?
声明新变量,并操作新变量

  1. func main() {
  2. var funcs []func()
  3. for i := 0; i < 3; i++ {
  4. n := i
  5. funcs = append(funcs, func() {
  6. fmt.Println(n)
  7. })
  8. }
  9. for j := 0; j < 3; j++ {
  10. funcs[j]()
  11. }
  12. }

创建新匿名函数并传入i

  1. func main() {
  2. var funcs []func()
  3. for i := 0; i < 3; i++ {
  4. func(i int) {
  5. funcs = append(funcs, func() {
  6. fmt.Println(i)
  7. })
  8. }(i)
  9. }
  10. for j := 0; j < 3; j++ {
  11. funcs[j]()
  12. }
  13. }

3、总结

  • 闭包=函数+相关引用环境
  • 闭包会导致闭包中使用的函数中的变量逃逸到堆上
  • 解决循环闭包的两种方法