原文链接:what happen with closures running as goroutine
由于循环变量的工作方式,在 Go 1.22 版本之前(参见本节末尾的更新),在使用闭包与并发时可能会引起一些混淆。考虑以下程序:
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
}
}
你可能错误地期望看到的输出是 a,b,c。但你可能实际看到的是 c,c,c。这是因为循环的每次迭代都使用变量 v 的同一实例,所以每个闭包都共享这个单一变量。当闭包运行时,它打印的是执行 fmt.Println 时的 v 的值,但是 v 可能在启动协程后已经被修改了。为了在问题发生之前帮助检测这个和其他问题,运行 go vet。
为了在启动每个闭包时将 v 的当前值绑定到闭包,必须修改内部循环以在每次迭代时创建一个新变量。一种方法是将变量作为闭包的参数传递:
for _, v := range values {
go func(u string) {
fmt.Println(u)
done <- true
}(v)
}
在这个例子中,v 的值作为参数传递给了匿名函数。然后在函数内部,这个值可以作为变量 u 来访问。
更简单的方法是创建一个新变量,使用一种在 Go 中可能看起来有些奇怪但却完全有效的声明方式:
for _, v := range values {
v := v // create a new 'v'.
go func() {
fmt.Println(v)
done <- true
}()
}
这种语言的行为,即不为每次迭代定义一个新变量,事后被认为是一个错误,并在 Go 1.22 中得到了解决,Go 1.22 确实为每次迭代创建了一个新变量,从而消除了这个问题。