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

Go 为什么这段代码会导致数据竞争?,go,concurrency,goroutine,data-race,Go,Concurrency,Goroutine,Data Race,m1和m2是不同的变量,为什么第16行和第11行会导致数据争用 我的围棋版本是1.8。我想这是一些编译优化,有人能告诉我吗?非常感谢。数据竞赛的要求如下: 多个goroutine同时访问同一资源(例如变量) 这些访问中至少有一个是写访问 访问是不同步的 在您的代码中,所有3项要求都得到满足: 您拥有访问m1的主goroutine,并且您在其中启动的goroutine也访问m1。主goroutine在另一个goroutine启动后访问它,因此它们是并发的 主goroutine将m1写入第17行:m

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

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

数据竞赛的要求如下:

  • 多个goroutine同时访问同一资源(例如变量)
  • 这些访问中至少有一个是写访问
  • 访问是不同步的
  • 在您的代码中,所有3项要求都得到满足:

  • 您拥有访问
    m1
    的主goroutine,并且您在其中启动的goroutine也访问
    m1
    。主goroutine在另一个goroutine启动后访问它,因此它们是并发的
  • 主goroutine将
    m1
    写入第17行:
    m1=m2
  • 访问不同步,您不使用互斥体或通道或类似的东西(睡眠是同步)
  • 因此,这是一场数据竞赛

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

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

    也就是在第17行之后,如果程序没有立即结束(可能,但不一定),那么启动的goroutine会尝试读取第16行最后写入的
    m1
    ,现在是
    m2
    ,因此这解释了第11行和第16行之间的“冲突”

    完整的
    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
    ==================
    

    数据竞争

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

    指令重新排序

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

    ==================
    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
    
    可以重新排序到

    m2["hello"] = 3
    m1 = m2
    

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

    似乎是行号混淆了。我看到了11和17之间的竞争。@Volker实际上我的go工具将两者都打印为数据竞争,#16是第一次数据竞争的结果,如我的回答中所述。是的,第17行和第11行是数据竞争,但我不明白为什么第16行和第11行是数据竞争,它显示在“竞争检测器打印信息”中@黄英俊 这是因为,如果启动的goroutine在第17行主goroutine中的赋值之后继续运行,并读取
    m1
    ,则可能是分配的
    m2
    ,最后写入第16行。请参阅编辑后的答案。谢谢。但我还是有些困惑。将m2分配给m1是在写入m2之后,这是否意味着这种情况永远不会发生,它只是一个竞赛检测器警告@黄英俊 竞赛检测器仅显示发生事件的警告。您启动goroutine,它一直读取
    m1
    ,然后写入
    m2
    ,然后将
    m2
    分配给
    m1
    ,因此启动的goroutine现在将读取同时修改的
    m2
    (在goroutine启动后),因此通过
    m1
    读取
    m2
    也是一种数据竞争,已启动的goroutine无法读取m2到m1。按顺序执行读取m1、写入m2和将m2分配给m1。这就是我所困惑的。如果是这种情况(重新排序导致了问题),那么通过向go工具传递
    -gcflags“-N-l”
    来禁用所有优化和内联不应该消除这种数据竞争吗?但事实并非如此。重新排序不是原因,因为无论怎样,
    m1=m2
    都是一个潜在的数据竞争点。重新排序应使其将分配计算为
    m2[“hello”]=3
    相当于
    m1[“hello”]=3
    我理解这一点,但上述标志不也应禁用重新排序吗?如果应该,则不会发生重新排序,因此
    m2[“hello”]=3
    将不等同于
    m1[“hello”]=3
    。谢谢。你能告诉我更多的细节吗?如果重新排序导致此数据竞争,我如何消除它?我需要做一个实验来帮助我更深入地理解这些原理。@icza指令重新排序不是任何错误的原因,探究答案。试图让我们证明比赛地点的合理性
    m2["hello"] = 3
    m1 = m2
    
    m1 = m2
    m2["hello"] = 3