package main
import (
\"fmt\"
\"time\"
)
type field struct {
name string
}
func (p *field) print() {
fmt.Println(p.name)
}
func main() {
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.