Go gotcha: Iteration variables and closures

Why does this program…

func main() {
	var wg sync.WaitGroup
	wg.Add(5)
	for i := 0; i < 5; i++ {
		go func() {
			fmt.Print(i)
			wg.Done()
		}()
	}
	wg.Wait()
	fmt.Println()
}

…print…

55555

(A WaitGroup waits for a collection of goroutines to finish.)

Answer

There is a data race: the variable i is shared by six (6) goroutines.

A data race occurs when two goroutines access the same variable concurrently and at least one of the accesses is a write.

(See Data races explained for a detailed discussion.)

To avoid this, use a local variable and pass the number as a parameter when starting the goroutine:

func main() {
	var wg sync.WaitGroup
	wg.Add(5)
	for i := 0; i < 5; i++ {
		go func(n int) { // Use a local variable.
			fmt.Print(n)
			wg.Done()
		}(i)
	}
	wg.Wait()
	fmt.Println()
}

Example output:

40123

It’s also possible to avoid this data race while still using a closure, but then we must take care to use a unique variable for each goroutine:

func main() {
	var wg sync.WaitGroup
	wg.Add(5)
	for i := 0; i < 5; i++ {
		n := i // Create a unique variable for each closure.
		go func() {
			fmt.Print(n)
			wg.Done()
		}()
	}
	wg.Wait()
	fmt.Println()
}

See Data races explained for a detailed epxlanation of data races in Go.

Comments