奇怪的goroutine和通道通信行为

huangapple go评论83阅读模式
英文:

Strange behaviour of goroutine and channel communication

问题

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(k, v string) {
fmt.Println(k, v)
completed <- true
}(k, v)
}
<- completed
<- completed
}

英文:
package main

import &quot;fmt&quot;

func main() {
  completed := make(chan bool, 2)
  m := map[string]string{&quot;a&quot;: &quot;a&quot;, &quot;b&quot;: &quot;b&quot;}
  for k, v := range m {
    go func() {
      fmt.Println(k, v)
      completed &lt;- true
    }()
  }
  &lt;- completed
  &lt;- 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?

答案1

得分: 6

这是一个经典的“竞争条件循环”示例。如果你用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 playground上也是这样的:http://play.golang.org/p/Orgn030Yfr

这是因为匿名函数引用了for k, v行中的变量,而不是这些变量在创建goroutine时的。首先,两个变量都被设置为一个值,并且生成了一个goroutine,然后它们被设置为另一个值,并且生成了另一个goroutine。然后,两个goroutine都运行,并且它们都看到了k和v的最新值。顺便说一下,这并不是特定于多线程或Go的(play.golang.org在单个线程中运行所有内容,仍然显示这个“bug”)。在JavaScript中也会出现同样的问题,因为那里保证只有一个线程:

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

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

英文:

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{&quot;a&quot;: &quot;a&quot;, &quot;b&quot;: &quot;b&quot;}
  for k, v := range m {
    go func(k, v string) {
      fmt.Println(k, v)
      completed &lt;- true
    }(k, v)
  }
  &lt;- completed
  &lt;- 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: &#39;a&#39;, b: &#39;b&#39;};
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.

答案2

得分: 1

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

英文:

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.

答案3

得分: 0

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

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

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

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

英文:

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

huangapple
  • 本文由 发表于 2013年6月14日 10:56:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/17100423.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定