What does this print in Go 1.22+ vs before?
Question 236MediumGo 1.22 to 1.25
func main() {
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func() {
defer wg.Done()
fmt.Print(i, " ")
}()
}
wg.Wait()
}
Go 1.22 and later (when go.mod declares go 1.22 or higher): each iteration gets its own i. The output is some permutation of 0 1 2, in any order because goroutine scheduling is nondeterministic.
Before Go 1.22, a single i was shared by every iteration. It typically printed 3 3 3, and it was also a data race, since the loop wrote i while the goroutines read it. The old workaround was i := i or passing i as an argument.
Notes:
- The semantics are chosen per module by the
goline ingo.mod, not by the toolchain version. Old modules built with a new compiler keep the old behavior. - The change applies to 3-clause
forloops and torangeloops. - The
loopclosurevet check is no longer needed for 1.22+ modules.
Follow-up: if the output must be in order, ordering has to come from synchronization, for example by writing into results[i] and printing after Wait.
More on Concurrency Patterns & sync
- Q234How does the race detector work, and what are its limitations? Is a "benign" data race ever OK?
- Q235Does this counter give the right answer with GOMAXPROCS=1? What about counter++ in general?
- Q237Implement a generic worker pool with a fixed number of workers, context cancellation, and no goroutine leaks.
- Q238Implement fan-out / fan-in: a generic Merge that combines N channels into one.
- Q239How do nil channels behave in select, and how are they used to merge two channels?
- Q240Build a cancellable pipeline. What causes goroutine leaks in pipelines and how do you prevent them?