Go 语言使用 goroutine 运行闭包的“坑”

开发 前端
本文我们介绍了在 for ... range ... 中,Go 语言在每次迭代时,没有定义一个新变量,导致使用 goroutine 运行闭包时,经常会掉“坑”。

​1.介绍

在 Go 语言中,函数支持匿名函数,闭包就是一种特殊的匿名函数,它可以用于访问函数体外部的变量。

需要注意的是,在 for ... range ... 中,使用 goroutine 执行闭包时,经常会掉“坑”。

因为匿名函数可以访问函数体外部的变量,而 for ... range ... 返回的 val 的值是引用的同一个内存地址的数据,所以匿名函数访问的函数体外部的 val 值是循环中最后输出的一个值。

2 .“踩坑”示例代码

func main() {
done := make(chan bool)

values := []string{"a", "b", "c"}
for _, v := range values {
go func() {
fmt.Println(v)
done <- true
}()
}

// wait for all goroutines to complete before exiting
for _ = range values {
<-done
}
}

输出结果:

c
c
c

阅读上面这段代码,在 for ... range ... 中,使用 goroutine 执行闭包,打印切片中的元素,实际输出结果不是我们期望得到的输出结果。

这是因为循环的每次迭代都使用相同的变量 v 实例,因此每个闭包共享该单个变量。我们可以在示例代码中简单修改,同时输出变量 v 的内存地址和值。

把 fmt.Println(v)​ 修改为 fmt.Printf("val=%s pointer=%p\n", v, &v)。

修改后的输出结果:

val=c pointer=0xc000010200
val=c pointer=0xc000010200
val=c pointer=0xc000010200

我们可以在输出结果中发现,打印变量 v 的内存地址都是 0xc000010200。

当闭包运行时,它会在执行 fmt.Println​ 时打印变量 v 的值,但 v 的值可能在 goroutine 启动后已被修改。感兴趣的读者朋友们可以使用 go vet 检查。

怎么避免“踩坑”呢?

一种方法是将变量作为参数传递给闭包:

func main() {
done := make(chan bool)

values := []string{"a", "b", "c"}
for _, v := range values {
go func(param string) {
// fmt.Println(v)
fmt.Printf("val=%s pointer=%p\n", param, &param)
done <- true
}(v)
}

// wait for all goroutines to complete before exiting
for _ = range values {
<-done
}
}

输出结果:

val=c pointer=0xc000010200
val=a pointer=0xc00009a000
val=b pointer=0xc0000a4000

阅读上面这段代码,通过将变量 v 的值作为参数传递给闭包,然后,该值作为形参 param 的值,在函数体内部被访问。

另外一种方法是创建一个新变量:

func main() {
done := make(chan bool)

values := []string{"a", "b", "c"}
for _, v := range values {
param := v
go func() {
// fmt.Println(v)
fmt.Printf("val=%s pointer=%p\n", param, &param)
done <- true
}()
}

// wait for all goroutines to complete before exiting
for _ = range values {
<-done
}
}

输出结果:

val=c pointer=0xc000082200
val=a pointer=0xc0000821e0
val=b pointer=0xc0000821f0

通过输出结果可以发现,该种方式也可以达到我们期望的结果。

3.总结

本文我们介绍了在 for ... range ... 中,Go 语言在每次迭代时,没有定义一个新变量,导致使用 goroutine 运行闭包时,经常会掉“坑”。

我们给出避免“踩坑”的两种方法,其中,第二种方法更简单。

参考资料:

https://go.dev/tour/moretypes/25

https://gobyexample.com/closures

https://pkg.go.dev/cmd/vet

https://go.dev/doc/faq#closures_and_goroutines

https://go.dev/doc/effective_go#goroutines

责任编辑:武晓燕 来源: Golang语言开发栈
相关推荐

2022-08-08 08:31:55

Go 语言闭包匿名函数

2021-09-30 09:21:28

Go语言并发编程

2021-10-26 13:18:52

Go底层函数

2023-11-01 15:54:59

2022-07-31 23:05:55

Go语言短变量

2021-10-28 19:10:02

Go语言编码

2024-01-22 09:51:32

Swift闭包表达式尾随闭包

2023-04-18 08:27:16

日志级别日志包

2023-10-13 19:42:00

2022-01-03 20:13:08

Gointerface 面试

2024-02-06 14:05:00

Go中间件框架

2023-07-11 08:46:38

闭包函数Rust

2014-09-02 10:39:53

Go语言C语言

2023-12-25 09:58:25

sync包Go编程

2021-07-06 07:46:07

Go语言编程

2009-07-22 07:43:00

Scala闭包

2021-06-29 23:40:19

Golang语言并发

2022-11-03 20:38:01

CMD命令Go

2021-02-06 18:19:54

TimeGo语言

2009-07-24 17:30:37

Javascript闭
点赞
收藏

51CTO技术栈公众号