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

Be the first to comment!