Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange behaviour of goroutine and channel communication

Tags:

go

package main

import "fmt"

func main() {
  completed := make(chan bool, 2)
  m := map[string]string{"a": "a", "b": "b"}
  for k, v := range m {
    go func() {
      fmt.Println(k, v)
      completed <- true
    }()
  }
  <- completed
  <- completed
}

I ran the code hundreds of times, the output is always:

b b
b b

However, I have never seen pair a a printed. Is this some sort of weird concurrency issue?


2 Answers

This is a classic example of a "Race on counter loop". If you run your code with go run -race I suspect it will tell you that.

The following will do what you expect:

func main() {
  completed := make(chan bool, 2)
  m := map[string]string{"a": "a", "b": "b"}
  for k, v := range m {
    go func(k, v string) {
      fmt.Println(k, v)
      completed <- true
    }(k, v)
  }
  <- completed
  <- completed
}

Your original code is likely to print only b's (or only a's), on any machine, and in fact it happens on the Go playground: http://play.golang.org/p/Orgn030Yfr

This is because the anonymous function is referring to the variables from the for k, v line, not the values that those variables happen to have at the moment the goroutine is created. First both variables are set to one value, and one goroutine is spawned, then they're set to the other value, and another goroutine is spawned. Then, both goroutines run, and they both see the newest values of k and v. By the way, this isn't really specific to multithreading or to Go (play.golang.org runs everything in a single thread and still shows this "bug.") This same problem happens in JavaScript where there is guaranteed to be only one thread:

obj = {a: 'a', b: 'b'};
for (k in obj) {
  setTimeout(function() { console.log(k, obj[k]); }, 0);
}

by the time the anonymous function runs, the for loop has finished, so 'k' is left with its most recent value for both runs of the function.

like image 107
Tyler Avatar answered Nov 27 '25 13:11

Tyler


You are passing no arguments to the goroutines. They're thus both using the same instances of k and v so as any value they have, in your case, after the range loop terminated. With GOMAXPROCS > 1, you moreover have a data race on those variables.

like image 38
zzzz Avatar answered Nov 27 '25 13:11

zzzz



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!