Goroutine和频道通讯的奇怪行为

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?

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);
}

http://goo.gl/vwrMQ -- 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.

According to the spec, "the iteration order over maps is not specified and is not guaranteed to be the same from one iteration to the next".

Basically, you shouldn't expect the iteration to be in any particular order. In what you are seeing, maybe the current implementation of range always produces this output, but it might be different under different conditions or in the next version of Go.

If you want to iterate over the map keys in a particular order, you can specify it yourself using a slice as described there:

http://blog.golang.org/go-maps-in-action

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.