348 lines
6.1 KiB
Markdown
348 lines
6.1 KiB
Markdown
|
# [Golang 闭包实例详解](https://blog.csdn.net/li_101357/article/details/80196650)
|
|||
|
|
|||
|
## 1.闭包的概念
|
|||
|
|
|||
|
闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。或者说是函数和其引用环境的组合体。闭包的概念可以直接百度或者 google 详细搜索详细,这里主要是通过几个实例来对闭包进行详解。
|
|||
|
|
|||
|
## 2.闭包实例详解
|
|||
|
|
|||
|
### 2.1.实例 1
|
|||
|
|
|||
|
```go
|
|||
|
package main
|
|||
|
|
|||
|
import "fmt"
|
|||
|
|
|||
|
//函数片段
|
|||
|
func add(base int) func(int) int {
|
|||
|
fmt.Printf("&base = %p\n", &base) //打印变量地址,可以看出来 内部函数时对外部传入参数的引用
|
|||
|
|
|||
|
f := func(i int) int {
|
|||
|
fmt.Printf("&base(in f) = %p\n", &base)
|
|||
|
base += i
|
|||
|
return base
|
|||
|
}
|
|||
|
|
|||
|
return f
|
|||
|
}
|
|||
|
|
|||
|
//由 main 函数作为程序入口点启动
|
|||
|
func main() {
|
|||
|
fmt.Println("-1.------")
|
|||
|
t1 := add(10)
|
|||
|
fmt.Println("")
|
|||
|
fmt.Println(t1(1), t1(2))
|
|||
|
fmt.Println("---------")
|
|||
|
|
|||
|
fmt.Println("\n-2.------")
|
|||
|
t2 := add(100)
|
|||
|
fmt.Println("")
|
|||
|
fmt.Println(t2(1), t2(2))
|
|||
|
fmt.Println("---------")
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
程序执行结果如下所示:
|
|||
|
|
|||
|
```bash
|
|||
|
-1.------
|
|||
|
&base = 0xc000014120
|
|||
|
|
|||
|
&base(in f) = 0xc000014120
|
|||
|
&base(in f) = 0xc000014120
|
|||
|
11 13
|
|||
|
---------
|
|||
|
|
|||
|
-2.------
|
|||
|
&base = 0xc000014128
|
|||
|
|
|||
|
&base(in f) = 0xc000014128
|
|||
|
&base(in f) = 0xc000014128
|
|||
|
101 103
|
|||
|
---------
|
|||
|
```
|
|||
|
|
|||
|
根据程序的执行结果可以看出来,内部函数是对外部变量引用。
|
|||
|
|
|||
|
重点:延迟调用有些知识点有异曲同工的地方,函数体内某个变量作为 defer 匿名函数的参数,则在定义 defer 时已获得值拷贝,否则引用某个变量的地址(引用拷贝)。代码片段如下所示:
|
|||
|
|
|||
|
```go
|
|||
|
package main
|
|||
|
|
|||
|
import "fmt"
|
|||
|
|
|||
|
//由 main 函数作为程序入口点启动
|
|||
|
func main() {
|
|||
|
x, y := 1, 2
|
|||
|
|
|||
|
defer func(a int) {
|
|||
|
fmt.Println("defer x = ", a, ", defer y = ", y) //y为闭包引用
|
|||
|
}(x) //x值拷贝 调用时传入参数
|
|||
|
|
|||
|
x += 100
|
|||
|
y += 200
|
|||
|
|
|||
|
fmt.Println("x =", x, ", y =", y)
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
程序执行结果:
|
|||
|
|
|||
|
```bash
|
|||
|
x = 101 , y = 202
|
|||
|
defer x = 1 , defer y = 202
|
|||
|
```
|
|||
|
|
|||
|
多次注册延迟调用,相反顺序执行:
|
|||
|
|
|||
|
```go
|
|||
|
package main
|
|||
|
|
|||
|
import "fmt"
|
|||
|
|
|||
|
func main() {
|
|||
|
for i := 0; i < 3; i++ {
|
|||
|
//多次注册延迟调用,相反顺序执行
|
|||
|
defer func(a int) {
|
|||
|
fmt.Println("defer a =", a) //闭包引用局部变量
|
|||
|
}(i)
|
|||
|
|
|||
|
defer func() {
|
|||
|
fmt.Println("defer i =", i) //闭包引用局部变量
|
|||
|
}()
|
|||
|
|
|||
|
fmt.Println("i =", i)
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
程序执行结果:
|
|||
|
|
|||
|
```bash
|
|||
|
i = 0
|
|||
|
i = 1
|
|||
|
i = 2
|
|||
|
defer i = 3
|
|||
|
defer a = 2
|
|||
|
defer i = 3
|
|||
|
defer a = 1
|
|||
|
defer i = 3
|
|||
|
defer a = 0
|
|||
|
```
|
|||
|
|
|||
|
### 2.2.实例 2
|
|||
|
|
|||
|
返回多个内部函数,程序片段如下所示:
|
|||
|
|
|||
|
```go
|
|||
|
package main
|
|||
|
|
|||
|
import "fmt"
|
|||
|
|
|||
|
//返回加减函数,重点:内部函数时对外部变量的引用
|
|||
|
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
|
|||
|
}
|
|||
|
|
|||
|
//由 main 函数作为程序入口点启动
|
|||
|
func main() {
|
|||
|
f1, f2 := calc(100)
|
|||
|
|
|||
|
fmt.Println(f1(1), f2(2)) //执行顺序:f1 f2 println
|
|||
|
fmt.Println(f1(3), f2(4))
|
|||
|
fmt.Println(f1(5), f2(6))
|
|||
|
fmt.Println(f1(7), f2(8))
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
程序执行结果:
|
|||
|
|
|||
|
```bash
|
|||
|
0xc00018c000
|
|||
|
0xc00018c000
|
|||
|
0xc00018c000
|
|||
|
101 99
|
|||
|
0xc00018c000
|
|||
|
0xc00018c000
|
|||
|
102 98
|
|||
|
0xc00018c000
|
|||
|
0xc00018c000
|
|||
|
103 97
|
|||
|
0xc00018c000
|
|||
|
0xc00018c000
|
|||
|
104 96
|
|||
|
```
|
|||
|
|
|||
|
### 2.3.实例 3
|
|||
|
|
|||
|
涉及 goroutine 时的情况,程序片段如下所示:
|
|||
|
|
|||
|
```go
|
|||
|
package main
|
|||
|
|
|||
|
import (
|
|||
|
"fmt"
|
|||
|
"time"
|
|||
|
)
|
|||
|
|
|||
|
//由 main 函数作为程序入口点启动
|
|||
|
func main() {
|
|||
|
|
|||
|
for i := 0; i < 5; i++ {
|
|||
|
go func() {
|
|||
|
fmt.Println(i) //i变量值也是引用.创建5个线程执行函数, for循环执行过程中可能执行完的时候,线程刚好处于i的某个值。
|
|||
|
}()
|
|||
|
|
|||
|
}
|
|||
|
time.Sleep(time.Second * 1)
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
程序运行结果:
|
|||
|
|
|||
|
```bash
|
|||
|
# Time 1
|
|||
|
5
|
|||
|
5
|
|||
|
5
|
|||
|
5
|
|||
|
5
|
|||
|
# Time 2
|
|||
|
5
|
|||
|
5
|
|||
|
5
|
|||
|
5
|
|||
|
0
|
|||
|
# Time 3
|
|||
|
3
|
|||
|
5
|
|||
|
5
|
|||
|
5
|
|||
|
5
|
|||
|
```
|
|||
|
|
|||
|
代码改进:
|
|||
|
|
|||
|
```go
|
|||
|
package main
|
|||
|
|
|||
|
import (
|
|||
|
"fmt"
|
|||
|
"time"
|
|||
|
)
|
|||
|
|
|||
|
//由 main 函数作为程序入口点启动
|
|||
|
func main() {
|
|||
|
|
|||
|
ch := make(chan int, 1)
|
|||
|
|
|||
|
for i := 0; i < 5; i++ {
|
|||
|
go func() {
|
|||
|
ch <- 1
|
|||
|
fmt.Println(i)
|
|||
|
}()
|
|||
|
|
|||
|
<-ch
|
|||
|
}
|
|||
|
time.Sleep(time.Second * 1)
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
程序执行结果:
|
|||
|
|
|||
|
```bash
|
|||
|
# Time 1
|
|||
|
0
|
|||
|
1
|
|||
|
2
|
|||
|
3
|
|||
|
4
|
|||
|
# Time 2
|
|||
|
0
|
|||
|
1
|
|||
|
2
|
|||
|
3
|
|||
|
4
|
|||
|
```
|
|||
|
|
|||
|
通过这三个示例的结果,闭包中的值是对源变量的引用。指向的是变量的当前值。
|
|||
|
|
|||
|
### 2.4.实例 4:闭包错误引起的死锁
|
|||
|
|
|||
|
最初使用 goroutine 的时候,曾遇到一个程序死锁的问题。思来想去看代码都觉得问题,最后发现是错误使用闭包导致的问题。
|
|||
|
|
|||
|
具体的代码如下所示:
|
|||
|
|
|||
|
```go
|
|||
|
package main
|
|||
|
|
|||
|
import (
|
|||
|
"fmt"
|
|||
|
)
|
|||
|
|
|||
|
func main() {
|
|||
|
//创建slice
|
|||
|
cs := make([](chan int), 10)
|
|||
|
for i := 0; i < len(cs); i++ {
|
|||
|
cs[i] = make(chan int)
|
|||
|
}
|
|||
|
|
|||
|
// 此处 for 循环为问题代码块
|
|||
|
for i := range cs {
|
|||
|
go func() {
|
|||
|
cs[i] <- i //创建线程,但是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]… 都阻塞起来了,从而导致了死锁。
|
|||
|
|
|||
|
两种修改方法如下:
|
|||
|
|
|||
|
```go
|
|||
|
for i := range cs {
|
|||
|
go func(index int) {
|
|||
|
cs[index] <- index
|
|||
|
}(i)
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
或
|
|||
|
|
|||
|
```go
|
|||
|
ch := make(chan int)
|
|||
|
for i := range cs {
|
|||
|
go func() {
|
|||
|
ch <- 1
|
|||
|
cs[i] <- i
|
|||
|
}()
|
|||
|
<-ch
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
## 3.外部参考资料
|
|||
|
|
|||
|
* <https://blog.csdn.net/libing_thinking/article/details/49889491>
|
|||
|
* <https://www.jianshu.com/p/fa21e6fada70>
|
|||
|
* <https://studygolang.com/articles/9747>
|