2
1 package main
2
3 import "time"
4
5 func main() {
6     m1 := make(map[string]int)
7     m1["hello"] = 1
8     m1["world"] = 2
9     go func() {
10         for i := 0; i < 100000000; i++ {
11             _ = m1["hello"]
12         }
13     }()
14     time.Sleep(100 * time.Millisecond)
15     m2 := make(map[string]int)
16     m2["hello"] = 3
17     m1 = m2
18 }

我用这段代码运行命令go run --race并得到:

==================
WARNING: DATA RACE
Read at 0x00c420080000 by goroutine 5:
  runtime.mapaccess1_faststr()
      /usr/local/go/src/runtime/hashmap_fast.go:208 +0x0
  main.main.func1()
      /Users/meitu/test/go/map.go:11 +0x80

Previous write at 0x00c420080000 by main goroutine:
  runtime.mapassign()
      /usr/local/go/src/runtime/hashmap.go:485 +0x0
  main.main()
      /Users/meitu/test/go/map.go:16 +0x220

Goroutine 5 (running) created at:
  main.main()
      /Users/meitu/test/go/map.go:13 +0x1aa
==================

m1并且m2是不同的变量,为什么第 16 行和第 11 行会导致数据竞争?

我的 Go 版本是 1.8。我想这是一些编译优化,有人可以告诉我吗?非常感谢。

4

2 回答 2

3

进行数据竞赛的要求是:

  1. 多个 goroutine同时访问同一个资源(例如一个变量)。
  2. 这些访问中至少有一个是write
  3. 访问是不同步的。

在您的代码中,所有 3 个要求都得到满足:

  1. 你有主 goroutine 访问m1,你开始的那个也访问m1。主 goroutine 在另一个 goroutine 启动后访问它,因此它们是并发的。
  2. 主 goroutine 写m1在第 17 行:m1 = m2.
  3. 访问不同步,您不使用互斥锁或通道或类似的东西(睡眠不是同步)。

因此,这是一场数据竞赛。

明显的数据竞争发生在第 11 行读取m1和第 17 行写入之间m1

但!由于第 17 行分配m2m1,那么当/如果启动的 goroutine 继续运行时,它会尝试读取m1现在可能是的值,m2因为我们分配m2m1。这是什么意思?这引入了另一个数据竞争写入m2和读取m1

那是在第 17 行之后,如果程序没有立即结束(它可能,但不一定),那么启动的 goroutine 会尝试从第 16 行最后一次写入的m1nowm2中读取,所以这解释了之间的“冲突”第 11 行和第 16 行。

完整go run -race输出如下:

==================
WARNING: DATA RACE
Write at 0x00c42000e010 by main goroutine:
  main.main()
      /home/icza/gows/src/play/play2.go:17 +0x22f

Previous read at 0x00c42000e010 by goroutine 5:
  [failed to restore the stack]

Goroutine 5 (running) created at:
  main.main()
      /home/icza/gows/src/play/play2.go:9 +0x190
==================
==================
WARNING: DATA RACE
Read at 0x00c42007e000 by goroutine 5:
  runtime.mapaccess1_faststr()
      /usr/local/go/src/runtime/hashmap_fast.go:208 +0x0
  main.main.func1()
      /home/icza/gows/src/play/play2.go:11 +0x7a

Previous write at 0x00c42007e000 by main goroutine:
  runtime.mapassign_faststr()
      /usr/local/go/src/runtime/hashmap_fast.go:598 +0x0
  main.main()
      /home/icza/gows/src/play/play2.go:16 +0x1fc

Goroutine 5 (running) created at:
  main.main()
      /home/icza/gows/src/play/play2.go:9 +0x190
==================
==================
WARNING: DATA RACE
Read at 0x00c420080088 by goroutine 5:
  main.main.func1()
      /home/icza/gows/src/play/play2.go:11 +0x90

Previous write at 0x00c420080088 by main goroutine:
  main.main()
      /home/icza/gows/src/play/play2.go:16 +0x212

Goroutine 5 (running) created at:
  main.main()
      /home/icza/gows/src/play/play2.go:9 +0x190
==================
Found 3 data race(s)
exit status 66
于 2017-09-15T07:15:51.187 回答
1

数据竞赛

当两个 goroutine 同时访问同一个变量并且至少其中一个访问是写入时,就会发生数据竞争。

说明重新排序

编译器和处理器可以重新排序在单个 goroutine 中执行的读取和写入,只要重新排序不会改变例程中的行为,它不会确保其他 goroutine 的行为不受影响

m2["hello"] = 3
m1 = m2

可以重新订购

m1 = m2
m2["hello"] = 3

这不会改变主程序的行为,因此竞态检查也会考虑评估竞态条件。现在我们已经m2["hello"] = 3引起了竞态条件,它打印出与原始行号相同的内容

于 2017-09-15T07:40:31.387 回答