3
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
}

我运行代码数百次,输出始终是:

b b
b b

但是,我从未见过双a a打印。这是某种奇怪的并发问题吗?

4

3 回答 3

6

这是“Race on counter loop”的经典例子。如果你运行你的代码,go run -race我怀疑它会告诉你。

以下将满足您的期望:

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
}

您的原始代码可能在任何机器上只打印 b(或只 a),实际上它发生在 Go 操场上:http ://play.golang.org/p/Orgn030Yfr

这是因为匿名函数引用的是行中的变量for k, v,而不是这些变量在创建 goroutine 时碰巧具有的值。首先将两个变量设置为一个值,并生成一个 goroutine,然后将它们设置为另一个值,并生成另一个 goroutine。然后,两个 goroutine 都运行了,它们都看到了 k 和 v 的最新值。顺便说一下,这并不是多线程或 Go 所特有的(play.golang.org 在一个线程中运行所有东西,并且仍然显示这个“错误。”)同样的问题发生在 JavaScript 中,保证只有一个线程:

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

http://goo.gl/vwrMQ -- 在匿名函数运行时,for 循环已经完成,所以对于函数的两次运行,'k' 都保留了它的最新值。

于 2013-06-14T04:39:16.063 回答
1

您没有向 goroutine 传递任何参数。因此,它们都使用相同的 k 和 v 实例,因此在您的情况下,它们在range 循环终止后具有任何值。如果 GOMAXPROCS > 1,您还会对这些变量进行数据竞争。

于 2013-06-14T04:54:51.790 回答
0

根据规范,“未指定映射的迭代顺序,也不保证从一次迭代到下一次迭代顺序相同”。

基本上,您不应期望迭代以任何特定顺序进行。在你所看到的情况下,也许当前的实现range总是会产生这个输出,但在不同的条件下或在下一个版本的 Go 中可能会有所不同。

如果您想以特定顺序迭代映射键,您可以使用切片自行指定,如下所述:

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

于 2013-06-14T03:08:44.543 回答