[英]How to understand this behavior of goroutine?
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)
}
why does this code print 3 "three" instead of "one" "two" "three" in any order? 为什么此代码以任何顺序打印3个“三”而不是“一个”,“两个”,“三个”?
There is a data race. 有一场数据竞赛。
The code implicitly takes address of variable v
when evaluating arguments to the goroutine function . 在评估goroutine函数的参数时,代码隐式获取变量
v
地址。 Note that the call v.print()
is shorthand for the call (&v).print()
. 请注意,调用
v.print()
是调用(&v).print()
简写 。
The loop changes the value of variable v
. 该循环更改变量
v
的值。
When goroutines execute, it so happens that v
has the last value of the loop. 当goroutines执行时,
v
就是循环的最后一个值。 That's not guaranteed. 不能保证。 It could execute as you expected.
它可以按预期执行。
It's helpful and easy to run programs with the race detector . 使用竞速检测器运行程序非常有用且容易。 This data race is detected and reported by the detector.
该数据争用由检测器检测并报告。
One fix is to create another variable scoped to the inside of the loop: 一种解决方法是创建另一个变量,范围位于循环内部:
for _, v := range data {
v := v // short variable declaration of new variable `v`.
go v.print()
}
With this change, the address of the inner variable v
is taken when evaluating the arguments to the goroutine. 进行此更改后,在评估goroutine的参数时将使用内部变量
v
的地址。 There is a unique inner variable v
for each iteration of the loop. 循环的每次迭代都有一个唯一的内部变量
v
。
Yet another way to fix the problem is use a slice of pointers: 解决该问题的另一种方法是使用切片指针:
data := []*field{ {"one"},{"two"},{"three"} } // note '*'
for _, v := range data {
go v.print()
}
With this change, the individual pointers in the slice are passed to the goroutine, not the address of the range variable v
. 进行此更改后,切片中的各个指针将传递给goroutine,而不是范围变量
v
的地址。
Another fix is to use the address of the slice element: 另一个解决方法是使用slice元素的地址:
data := []field{ {"one"},{"two"},{"three"} } // note '*'
for i:= range data {
v := &data[i]
go v.print()
}
Because pointer values are typically used with types having a pointer receiver, this subtle issue does not come up often in practice. 由于指针值通常与具有指针接收器的类型一起使用,因此在实践中通常不会出现这种细微问题。 Because
field
has a pointer receiver, it would be typical to use []*field
instead of []field
for the type of data
in the question. 由于
field
具有指针接收器,因此对于问题中的data
类型,通常使用[]*field
而不是[]field
。
If the goroutine function is in an anonymous function, then a common approach for avoiding the issue is to pass the range variables as an argument to the anonymous function: 如果goroutine函数在匿名函数中,则避免该问题的常用方法是将范围变量作为参数传递给匿名函数:
for _, v := range data {
go func(v field) {
v.print() // take address of argument v, not range variable v.
}(v)
}
Because the code in the question does not already use an anonymous function for the goroutine, the first approach used in this answer is simpler. 因为问题中的代码尚未对goroutine使用匿名函数,所以此答案中使用的第一种方法更为简单。
As stated above there's a race condition it's result depends on delays on different processes and not well defined and predictable. 如上所述,有一个竞争条件,其结果取决于不同流程的延迟,并且定义不明确且不可预测。 For example if you add
time.Sleep(1*time.Seconds)
you likely to get a correct result. 例如,如果添加
time.Sleep(1*time.Seconds)
,则可能会得到正确的结果。 Because usually goroutine prints faster than 1second and will have correct variable v
but it's a very bad way. 因为通常goroutine的打印速度超过1秒,并且将具有正确的变量
v
但这是一种非常糟糕的方法。
Golang has a special race detector tool which helps to find such situations. Golang有一个特殊的种族检测器工具,可以帮助发现这种情况。 I recommend read about it while reading
testing
. 我建议在阅读
testing
时阅读它。 Definitely it's worth it. 绝对值得。
There's another way - explicitly pass variable value at goroutine start: 还有另一种方法-在goroutine开始时显式传递变量值:
for _, v := range data {
go func(iv field) {
iv.print()
}(v)
}
Here v
will be copied to iv
(“internal v”) on every iteration and each goroutine will use correct value. 这里
v
将在每次迭代时复制到iv
(“内部v”),并且每个goroutine将使用正确的值。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.