前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go 语言使用 goroutine 运行闭包的“坑”

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

作者头像
frank.
发布2022-10-28 10:32:03
9600
发布2022-10-28 10:32:03
举报
文章被收录于专栏:Golang语言开发栈

01

介绍

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

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

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

02

“踩坑”示例代码

代码语言:javascript
复制
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
 }
}

输出结果:

代码语言:javascript
复制
c
c
c

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

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

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

修改后的输出结果:

代码语言:javascript
复制
val=c pointer=0xc000010200
val=c pointer=0xc000010200
val=c pointer=0xc000010200

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

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

怎么避免“踩坑”呢?

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

代码语言:javascript
复制
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
 }
}

输出结果:

代码语言:javascript
复制
val=c pointer=0xc000010200
val=a pointer=0xc00009a000
val=b pointer=0xc0000a4000

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

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

代码语言:javascript
复制
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
 }
}

输出结果:

代码语言:javascript
复制
val=c pointer=0xc000082200
val=a pointer=0xc0000821e0
val=b pointer=0xc0000821f0

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

03

总结

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

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

推荐阅读:

  1. Golang 语言 vendor 在 GOPATH 和 Modules 中的区别
  2. Golang 语言中的内置函数 make 和 new
  3. Golang 语言的编程技巧之变量
  4. Golang 语言怎么使用 panic 函数?
  5. Go语言学习之函数

参考资料:

  1. https://go.dev/tour/moretypes/25
  2. https://gobyexample.com/closures
  3. https://pkg.go.dev/cmd/vet
  4. https://go.dev/doc/faq#closures_and_goroutines
  5. https://go.dev/doc/effective_go#goroutines
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-08-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Go语言开发栈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档