I'm looking at the typical data races in the Golang documentation, and I don't quite understand why there is a problem with this program:
func main() {
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++ {
go func() {
fmt.Println(i) // Not the 'i' you are looking for.
wg.Done()
}()
}
wg.Wait()
}
It prints 5, 5, 5, 5, 5 when I would expect it to print 0, 1, 2, 3, 4 (not necessarily in this order).
The way I see it, when the goroutine gets created inside the loop, the value of i is known (for instance, one could do a log.Println(i) at the beginning of the loop and see the expected value). So I would expect the goroutine to capture the value of i when it gets created and use that later on.
Obviously it's not what's happening but why?
Your function literal references the i from the outer scope. If you request the value of i, you get the value of whatever i is right now. In order to use the value of i at the time the Go routine was created, supply an argument:
func main() {
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++ {
go func(i int) {
fmt.Println(i)
wg.Done()
}(i)
}
wg.Wait()
}
runnable example
The variable i is not declared within the function literal, so it becomes part of a closure. An easy way how to understand closures is to think about how can they be implemented. The simple solution is using a pointer. You can think that the function literal is rewritten by the compiler into some
func f123(i *int) {
fmt.Println(*i)
wg.Done
}
On invocation of this function, by the go statement, the address of the i variable is passed to the called f123 (example name generated by the compiler).
You're probably using default GOMAXPROCS==1, so the for loop executes 5 times without any scheduling as the loop does no I/O or other "schedule points", such as channel operations.
When the loop terminates, with i == 5, the wg.Wait finally triggers execution of the five, ready to run, goroutines (for f123). All of them have of course the same pointer to the same integer variable i.
Every goroutine now sees the same i value of 5.
You might get different output when running with GOMAXPROCS > 1, or when the loop yields control. That can be done also by, for example, runtime.Gosched.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With