简体   繁体   中英

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?

There is a data race.

The code implicitly takes address of variable v when evaluating arguments to the goroutine function . Note that the call v.print() is shorthand for the call (&v).print() .

The loop changes the value of variable v .

When goroutines execute, it so happens that v has the last value of the loop. 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. There is a unique inner variable v for each iteration of the loop.

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 .

Another fix is to use the address of the slice element:

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.

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:

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.

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. Because usually goroutine prints faster than 1second and will have correct variable v but it's a very bad way.

Golang has a special race detector tool which helps to find such situations. I recommend read about it while reading testing . Definitely it's worth it.

There's another way - explicitly pass variable value at goroutine start:

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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