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
。这是一种奇怪的并发问题吗?
答案 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中可能会有所不同。
如果您想按特定顺序迭代地图键,可以使用切片自行指定它,如下所述: