闭包概念
所谓闭包是指内层函数引用了外层函数中的变量或称为引用了自由变量的函数,其返回值也是一个函数
1 2 3
| 闭包 = 函数 + 引用环境
接受一个或多个函数作为输入 输出一个函数
|
函数
函数只是一段可执行代码,编译后就“固化”了,每个函数在内存中只有一份实例,得到函数的入口点便可以执行函数了。
在函数式编程语言中,函数是一等公民(First class value):第一类对象,我们不需要像命令式语言中那样借助函数指针,委托操作函数,函数可以作为另一个函数的参数或返回值,可以赋给一个变量。函数可以嵌套定义,即在一个函数内部可以定义另一个函数,有了嵌套函数这种结构,便会产生闭包问题
匿名函数
函数可以像普通的类型(整型、字符串等)一样进行赋值、作为函数的参数传递、作为函数的返回值等。
匿名函数可以动态的创建,与之成对比的常规函数必须在包中编译前就定义完毕。匿名函数可以随时改变功能
Golang的函数只能返回匿名函数!
闭包实例
实例1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| func add(base int) func(int) int {
fmt.Printf("%p\n", &base)
f := func(i int) int { fmt.Printf("%p\n", &base) base += i return base } return f }
func main() { t1 := add(10) fmt.Println(t1(1), t1(2)) t2 := add(100) fmt.Println(t2(1), t2(2))
}
|
运行结果为
1 2 3 4 5 6 7 8 9
| 0xc0000b4008 0xc0000b4008 0xc0000b4008 11 13 0xc0000b4020 0xc0000b4020
0xc0000b4020 101 103
|
可以看出:
-
函数add返回一个函数,返回的这个函数就是闭包
-
调用同一个闭包函数,都是对同一个环境进行操作
-
函数add每进入一次,就形成了一个新的环境,对应的闭包中,函数都是同一个函数,环境却是引用不同的环境
实例2.1:
1 2 3 4 5 6 7 8 9 10 11 12 13
| func main() { x, y := 1,2
defer func(a int){ fmt.Println("defer x, y = ", a, y) }(x)
x += 100 y += 200
fmt.Println(x, y) }
|
结果为
1 2
| 101 202 defer x, y = 1 202
|
闭包对外的变量是引用
实例2.2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| func main() { for i := 0; i < 3; i++ { defer func(){ fmt.Println(i) }()
fmt.Print(i) if i == 2 { fmt.Printf("\n") } } }
|
结果为
实例3:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| func calc(base int) (func(int) int, func(int) int) {
fmt.Printf("%p\n", &base) add := func(i int) int { fmt.Printf("%p\n", &base) base += i return base }
sub := func(i int) int { fmt.Printf("%p\n", &base) base -= i return base }
return add, sub }
func main() { f1, f2 := calc(100) fmt.Println(f1(1), f2(2)) fmt.Println(f1(3), f2(4)) fmt.Println(f1(5), f2(6)) fmt.Println(f1(7), f2(8)) }
|
结果为
1 2 3 4 5 6 7 8 9 10 11 12 13
| 0xc00001a088 0xc00001a088 0xc00001a088 101 99 0xc00001a088 0xc00001a088 102 98 0xc00001a088 0xc00001a088 103 97 0xc00001a088 0xc00001a088 104 96
|
可以看出:
-
可利用go特性,同时返回两个闭包函数
- 相同父环境得两个闭包函数,使用的是相同的内存变量
实例4:
1 2 3 4 5 6 7 8 9 10 11
| func main() {
for i:=0; i<5; i++ { go func(){ fmt.Println(i) }() } time.Sleep(time.Second * 1) }
|
结果为
闭包中的值是对源变量的引用。指向的是变量的当前值。当延迟调用函数体内某个变量作为defer匿名函数的参数,则在定义defer时已获得值拷贝,否则引用某个变量的地址(引用拷贝)
实例5(阻塞):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| func main() { cs := make([](chan int), 10) for i := 0; i < len(cs); i++ { cs[i] = make(chan int) }
for i := range cs { go func() { cs[i] <- i }() }
for i := 0; i < len(cs); i++ { t := <-cs[i] fmt.Println(t) } }
|
主要功能就是 创建10个线程执行函数,并向channal写入值。由于goroutine还没有开始,i的值已经跑到了最大9,使得这几个goroutine都取的i=9这个值,从而都向cs[9]发消息,导致执行t := <-cs[i]时,cs[0]、cs[1]、cs[2] … 都阻塞起来了,从而导致了死锁
方案1(利用闭包传每次循环得参数,利用参数进行值传递):
1 2 3 4 5
| for i := range cs { go func(index int) { cs[index] <- index }(i) }
|
方案2(利用阻塞管道,每次等待协程起来再执行下一次):
1 2 3 4 5 6 7 8
| ch := make(chan int) for i := range cs { go func() { ch <- 1 cs[i] <- i }() <- ch }
|
参考链接
- https://tiancaiamao.gitbooks.io/go-internals/content/zh/03.6.html