为什么这段代码导致数据竞争?

时间:2017-09-15 07:07:14

标签: go concurrency goroutine data-race

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
==================

m1m2是不同的变量,为什么第16行和第11行导致数据竞争?

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

2 个答案:

答案 0 :(得分:3)

进行数据竞争的要求是:

  1. 多个goroutine 访问同一资源(例如变量)并发
  2. 这些访问中至少有一个是 write
  3. 访问权限是 uncynchronized
  4. 在您的代码中,满足所有3个要求:

    1. 您拥有主要的goroutine访问权限m1,而您在其中启动的访问权限也会访问m1。主goroutine在启动另一个goroutine之后访问它,因此它们是并发
    2. 主要goroutine在第17行写m1m1 = m2
    3. 访问不同步,您不使用互斥锁或频道或类似的东西(睡眠同步)。
    4. 因此,这是一场数据竞赛。

      显而易见的数据竞争是在第11行读取m1和第17行写m1之间。

      但是!由于第17行将m2分配给m1,然后当/如果启动的goroutine继续运行,它会尝试读取m1 现在可能m2的{​​{1}},因为我们已将m2分配给m1。这是什么意思? 这引入了另一个数据竞赛写作m2和阅读m1

      如果程序没有立即结束(可能,但不一定),那么就在#17行之后,那么启动的goroutine会尝试从最后写的m1读取m2在第16行,所以这解释了第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
      

答案 1 :(得分:0)

数据竞赛

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

说明重新排序

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

m2["hello"] = 3
m1 = m2

可以重新订购

m1 = m2
m2["hello"] = 3

这不会改变主程序的行为,因此比赛检查也会考虑评估竞争条件。现在我们有m2["hello"] = 3导致竞争条件,并且打印出与原始行号相同的