简体   繁体   English

如何理解goroutine的这种行为?

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM