Go Language concurrency and an easy pitfall

What should this code print?

package main

import (
  "fmt"
  "sync"
)

func main() {
  
  arr := []int {1, 2, 3}

  var wg sync.WaitGroup
  for i := 0; i < len(arr); i++ {
    wg.Add(1)
    // Start a thread to do some heavy work in the background
    go func() {
      fmt.Printf("i is %d\n", i)
      wg.Done()
    }()
  }
  // Wait till all the threads finish
  wg.Wait()
}

Instead of printing

i is 0
i is 1
i is 2

It generates

i is 3
i is 3
i is 3

What happened?

Turns out that the reference to i is captured but not the value. So when the internal anonymous function runs, it gets the current value of i at the time of execution and not creation.

One general rule for this is to never access value from the outer function directly, instead pass those values as parameters.

Consider this.

package main

import (
  "fmt"
  "sync"
)

func main() {
  
  arr := []int {1, 2, 3}

  var wg sync.WaitGroup
  for i := 0; i < len(arr); i++ {
    wg.Add(1)
    // Start a thread to do some heavy work in the background
    go func(j int) {
      fmt.Printf("i is %d\n", j)
      wg.Done()
    }(i)  // Capture the value at the time of thread creation
  }
  // Wait till all the threads finish
  wg.Wait()
}

And it produces

i is 2
i is 0
i is 1

 

  • Category: all

Leave a Reply

Your email address will not be published. Required fields are marked *