go语言闭包

各自努力,最高处见!加油!

go语言闭包

什么是闭包?

闭包的概念:是可以包含*(未绑定到特定对象)变量的代码块,这些变量不在这个代码块内或者任何全局上下文中定义,而是在定义代码块的环境中定义。要执行的代码块(由于*变量包含在代码块中,所以这些*变量以及它们引用的对象没有被释放)为*变量提供绑定的计算环境(作用域)。

简单来说就是包含在代码块中的*变量和引用对象在作用域结束时并没有被释放,已经与被执行的代码块绑定到一起。 Go语言中的闭包同样也会引用到函数外的变量。闭包的实现确保只要闭包还被使用,那么被闭包引用的变量会一直存在。

所以:闭包=函数+引用环境

认识闭包

package main

import (
    "fmt"
)

func squares() func() int {
    var x int
    return func() int {
        x++
        return x * x
    }
}

func main() {
    f1 := squares()
    f2 := squares()

    fmt.Println("first call f1:", f1())
    fmt.Println("second call f1:", f1())
    fmt.Println("first call f2:", f2())
    fmt.Println("second call f2:", f2())
}

go语言闭包

通过上面一段代码来认识一下闭包。
squares()函数的返回值是一个函数,返回的这个函数就是一个闭包。这个函数里面使用了squares()函数中定义的x变量。
根据闭包的概念,这个x变量不会被销毁,一直存在。当在main函数中第一次调用 f1() 的时候,x++,值为1.第二次调用f1()的时候,x++值为2.

那为什么调用f2()返回的数据又从1开始了呢?

f1跟f2引用的是不同的环境,在调用x++时修改的不是同一个x,因此两个函数的第一次输出都是1。函数squares每进入一次,就形成了一个新的环境,对应的闭包中,函数都是同一个函数,环境却是引用不同的环境。

上例的x变量存放在哪里?

func f() *Cursor {
    var c Cursor
    c.X = 500
    noinline()
    return &c
}

Cursor是一个结构体,这种写法在C语言中是不允许的,因为变量c是在栈上分配的,当函数f返回后c的空间就失效了。但是,在Go语言规范中有说明,这种写法在Go语言中合法的。语言会自动地识别出这种情况并在堆上分配c的内存,而不是函数f的栈上。

所以在闭包环境中,引用的变量内存是在堆中分配。返回闭包并不是单纯返回一个函数,而是返回一个结构体,记录下函数返回地址和引用的环境中的变量地址。

type Closure struct {
   F func()()
   i  *int   
}

理解闭包和普通函数调用的区别

代码示例1

package main

import (
  "fmt"
)

func main() {
   a := []int{1, 2, 3}
   for _, value := range a {
     fmt.Println(value)
     defer p(value)
   }
}
 
func p(value int) {
  fmt.Println(value)
}

运行结果

1
2
3
3
2
1

这个例子是普通的函数调用,每次调用p()时完成value的复制,等到返回的时候再执行。

对defer的理解: defer修饰的函数会将传入它的参数拷贝保存在自己的内存区域,等到函数返回时,才开始执行。由于defer是后进先出,所以执行变成3,2,1

代码示例2——为什么返回的三个值都是3呢?

package main

import (
  "fmt"
)

func main() {
  a := []int{1, 2, 3}
  for _, value := range a {
    fmt.Println(value)
    defer func() {
       fmt.Println(value)
    }()
    }
}

运行结果:

1
2
3
3
3
3

理解for…range的用法
在Go的for…range循环中,Go始终使用值拷贝的方式代替被遍历的元素本身,简单来说,就是for…range中那个value,是一个值拷贝,而不是元素本身。也是说value是个局部变量,只是把元素赋值给该变量而已。

理解闭包非传递参数外部变量值传的是引用
闭包里的非传递参数外部变量值是传引用的,也就是闭包是地址引用。在闭包函数里那个value就是外部不是闭包函数自己的参数,所以是相当于引用了外部的变量。

所以:闭包是通过地址引用来引用环境中的变量value,因此每次只是把value的地址拷贝了一份儿,就这样拷贝了三次。而执行到最后时value值为3,所以打印了3次value地址指向的值,所以是3,3,3。

转载自:https://www.cnblogs.com/ralap7/p/9195677.html
用自己理解的方式做了修改

回到我自己遇到的问题

对于以下代码,描述正确的是:

package main

import (
	"fmt"
	"sync"
)

func main() {
	var wg sync.WaitGroup
	intSlice := []int{1, 2, 3, 4, 5}
	wg.Add(len(intSlice))
	ans1, ans2 := 0, 0
	for _, v := range intSlice {
		vv := v
		go func() {
			defer wg.Done()
			ans1 += v
			ans2 += vv
		}()
	}
	wg.Wait()
	fmt.Printf("ans1:%v,ans2:%v", ans1, ans2)
	return
}

go语言闭包
我说一下自己的理解:go出去的每一个协程都可以理解成一个闭包,闭包绑定的是当前v的地址。v是for…range结构的接收变量,for函数在不断执行,v的值也在不断变化,所以协程中+=v并不是加一个固定的数字,具体的值需要视协程与主协程的时间而定。实测显示ans1的值一般都在20以上。

在for循环执行过程中,每次循环都创建一个局部变量vv,值为v的拷贝。闭包绑定这个局部变量vv的地址(这个vv本来的作用域只在for循环内,当次循环结束就应该被销毁,下次循环重新创建,因为这个变量被闭包绑定了,所以这个变量保存到了堆中,它的值一次为1,2,3,4,5).所以ans2的值为15。
go语言闭包

上一篇:mongodb分布式集群搭建手记


下一篇:PHP混合Go协程并发