Goroutines in loops may not behave as expected before Go 1.22
Update: this has been fixed in Go 1.22 and the following contents is left for historical reasons.
This was actually documented in CommonMistakes wiki page of the official golang’s GitHub repository (notice, this is the only common mistake documented there as this page is being written) but since its visibility is not good enough, it’s always a good start.
Code
Consider the following code:
package main
import "fmt"
func main() {
ch := make(chan int)
slice := []int{0, 1}
for _, v := range slice {
go func() { ch <- v }()
}
fmt.Println(<-ch)
fmt.Println(<-ch)
}
One may expect this to print:
0
1
But actually this code is not deterministic, and in most cases will just print
1
1
Why?
When iterating using i, v := range whatever
, the i
and v
variables are defined only once for the scope, and then their values are overwritten.
Since the goroutines reference the same variable all the time, but they are probably executed after the for
loop is finished, they all access the same last value of it: 1
.
You can check this by printing the pointer address, which never changes:
package main
import "fmt"
func main() {
for _, v := range []int{0, 1} {
fmt.Printf("%p\n", &v)
}
}
Related
for ;;
This can be also unexpected if we do:
package main
import "fmt"
func main() {
ch := make(chan int)
for i := 0; i < 2; i++ {
go func() { ch <- i }()
}
fmt.Println(<-ch)
fmt.Println(<-ch)
}
Where the output is:
2
2
Although our goroutines should have never been executed with i >= 2
since i < 2
is the stop condition for the loop.
Receiver call
As a corolary, but still worth mentioning, this also applies to receiver functions, as a simple example:
package main
import (
"fmt"
"sync"
)
type S string
func (s S) Print() {
fmt.Println(s)
}
func main() {
slice := []S{"a", "b"}
wg := sync.WaitGroup{}
wg.Add(2)
for _, s := range slice {
go func() {
s.Print()
wg.Done()
}()
}
wg.Wait()
}
Prints:
b
b