package main
import (
"fmt"
"time"
)
type field struct {
name string
}
func (p *field) print() {
fmt.Println(p.name)
}
func main() {
data := []field{ {"one"},{"two"},{"three"} }
for _,v := range data {
go v.print()
}
<-time.After(1 * time.Second)
}
为什么这段代码以任何顺序打印3“三”而不是“一”“两”“三”?
答案 0 :(得分:4)
有数据竞争。
在evaluating arguments to the goroutine function时,代码隐含地获取变量v
的地址。请注意,来电v.print()
为shorthand for the call (&v).print()
。
循环更改变量v
的值。
当goroutines执行时,v
具有循环的最后一个值。这不能保证。它可以按照您的预期执行。
使用race detector运行程序非常有用且简单。检测器检测并报告此数据竞争。
一个解决方法是创建另一个作用于循环内部的变量:
for _, v := range data {
v := v // short variable declaration of new variable `v`.
go v.print()
}
通过此更改,在评估goroutine的参数时会获取内部变量v
的地址。循环的每次迭代都有一个唯一的内部变量v
。
另一种解决问题的方法是使用指针:
data := []*field{ {"one"},{"two"},{"three"} } // note '*'
for _, v := range data {
go v.print()
}
通过此更改,切片中的各个指针将传递给goroutine,而不是范围变量v
的地址。
另一个解决方法是使用切片元素的地址:
data := []field{ {"one"},{"two"},{"three"} } // note '*'
for i:= range data {
v := &data[i]
go v.print()
}
因为指针值通常与具有指针接收器的类型一起使用,所以这个微妙的问题在实践中并不经常出现。由于field
有一个指针接收器,因此对于问题中的[]*field
类型,通常使用[]field
而不是data
。
如果goroutine函数在匿名函数中,那么避免该问题的常用方法是将范围变量作为参数传递给匿名函数:
for _, v := range data {
go func(v field) {
v.print() // take address of argument v, not range variable v.
}(v)
}
因为问题中的代码尚未使用goroutine的匿名函数,所以此答案中使用的第一种方法更简单。
答案 1 :(得分:1)
如上所述,存在竞争条件,其结果取决于不同过程的延迟,并且没有明确定义和可预测。
例如,如果添加time.Sleep(1*time.Seconds)
,您可能会得到正确的结果。因为通常goroutine打印速度超过1秒并且具有正确的变量v
,但这是一种非常糟糕的方式。
Golang有一个特殊的竞赛检测工具,有助于找到这种情况。我建议在阅读testing
时阅读相关内容。绝对是值得的。
还有另一种方法 - 在goroutine start显式传递变量值:
for _, v := range data {
go func(iv field) {
iv.print()
}(v)
}
此处v
将在每次迭代时复制到iv
(“内部v”),每个goroutine将使用正确的值。