Go gotchas - loop variable and goroutines
Mar 5, 2016 · 2 minute readAnother Golang issue with goroutines and for loops today :) This time let’s assume we start with a simple for loop that calls an anonymous function:
package main
import (
"fmt"
"sync"
)
func main() {
numbers := []int{1, 2, 3, 4, 5, 6}
// WaitGroup will be used to wait for child goroutines
var wg sync.WaitGroup
for _, n := range numbers {
wg.Add(1)
func foo() {
fmt.Printf("%d ", n)
wg.Done()
}()
}
wg.Wait()
}
1 2 3 4 5 6
but to run the anonymous function in child goroutines - we will add a go
keyword before the function call:
package main
import (
"fmt"
"sync"
)
func main() {
numbers := []int{1, 2, 3, 4, 5, 6}
// WaitGroup will be used to wait for child goroutines
var wg sync.WaitGroup
for _, n := range numbers {
wg.Add(1)
go func foo() {
fmt.Printf("%d ", n)
wg.Done()
}()
}
wg.Wait()
}
6 6 6 6 6 6
What’s wrong? We see that all goroutines see the same value of n, and the value they see is equal to the last value of this variable. This suggests that goroutines access the variable not when they are started, but at a later time, when the for loop has run through all elements of numbers
.
This is in fact true - the anonymous function closes over the variable, and uses it’s value from the time it was executing, not from the time it was started. To fix the issue we can do two things - copy the loop variable to the for block:
for _, n := range numbers {
wg.Add(1)
var n = n
go func foo() {
fmt.Printf("%d ", n)
wg.Done()
}()
}
for _, n := range numbers {
wg.Add(1)
go func foo(n int) {
fmt.Printf("%d ", n)
wg.Done()
}(n)
}