goroutine和渠道沟通的奇怪行为

时间:2013-06-14 02:56:36

标签: 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
}

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

b b
b b

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

3 个答案:

答案 0 :(得分:5)

这是"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'将保留其最近的两次运行值。

答案 1 :(得分:1)

你没有向goroutines传递任何参数。因此,它们都使用相同的k和v实例,因此它们具有任何值,在您的情况下,范围循环终止后。使用GOMAXPROCS&gt; 1,你还要对这些变量进行数据竞争。

答案 2 :(得分:0)

根据the spec,“未指定地图上的迭代顺序,并且不保证从一次迭代到下一次迭代是相同的。”

基本上,您不应期望迭代具有任何特定顺序。在您看到的内容中,range的当前实现可能总是产生此输出,但在不同条件下或在下一版本的Go中可能会有所不同。

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

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

相关问题