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

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

Why does this code cause data race?

问题

以下是代码的翻译:

package main

import "time"

func main() {
    m1 := make(map[string]int)
    m1["hello"] = 1
    m1["world"] = 2
    go func() {
        for i := 0; i < 100000000; i++ {
            _ = m1["hello"]
        }
    }()
    time.Sleep(100 * time.Millisecond)
    m2 := make(map[string]int)
    m2["hello"] = 3
    m1 = m2
}

我使用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
==================

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

我的Go版本是1.8。我猜测这是一种编译优化,有人可以告诉我更多信息吗?非常感谢。

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

I run command go run --race with this code and get:

==================
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 and m2 are different variables,why do line 16 and line 11 cause data race?

My go version is 1.8. I guess that is some compile optimization, and somebody can tell me about it? Thank you very much.

答案1

得分: 3

满足数据竞争的要求有:

  1. 多个 goroutine 同时访问相同的资源(例如变量)。
  2. 这些访问中至少有一个是写操作。
  3. 这些访问没有进行同步。

在你的代码中满足了这三个要求:

  1. 你有主 goroutine 访问 m1,并且在其中启动的 goroutine 也访问 m1。主 goroutine 在另一个 goroutine 启动之后访问它,所以它们是并发的。
  2. 主 goroutine 在第 17 行写入了 m1m1 = m2
  3. 这些访问没有进行同步,你没有使用互斥锁、通道或其他同步机制(睡眠不是同步操作)。

因此,这是一个数据竞争。

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

但是!由于第 17 行将 m2 赋值给了 m1,那么当/如果启动的 goroutine 继续运行时,它会尝试读取 m1,而此时 m1 可能是 m2 的值,因为我们将 m2 赋值给了 m1。这意味着什么?这引入了另一个数据竞争,写入 m2 并读取 m1

这就是在第 17 行之后,如果程序不立即结束(可能会,但不一定),则启动的 goroutine 尝试从 m1 读取的原因,而此时 m1m2,它是在第 16 行最后写入的,所以这解释了第 11 行和第 16 行之间的“冲突”。

完整的 go run -race 输出如下:

==================
警告:数据竞争
主 goroutine 写入于 0x00c42000e010:
  main.main()
      /home/icza/gows/src/play/play2.go:17 +0x22f

之前的读取于 goroutine 5 于 0x00c42000e010:
  [无法恢复堆栈]

goroutine 5(正在运行)创建于:
  main.main()
      /home/icza/gows/src/play/play2.go:9 +0x190
==================
==================
警告:数据竞争
goroutine 5 读取于 0x00c42007e000:
  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

之前的写入于主 goroutine 于 0x00c42007e000:
  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(正在运行)创建于:
  main.main()
      /home/icza/gows/src/play/play2.go:9 +0x190
==================
==================
警告:数据竞争
goroutine 5 读取于 0x00c420080088:
  main.main.func1()
      /home/icza/gows/src/play/play2.go:11 +0x90

之前的写入于主 goroutine 于 0x00c420080088:
  main.main()
      /home/icza/gows/src/play/play2.go:16 +0x212

goroutine 5(正在运行)创建于:
  main.main()
      /home/icza/gows/src/play/play2.go:9 +0x190
==================
发现 3 个数据竞争
退出状态 66
英文:

The requirements to have a data race are:

  1. Multiple goroutines accessing the same resource (e.g. a variable) concurrently.
  2. At least one of those accesses is a write.
  3. The accesses are uncynchronized.

In your code all 3 requirements are met:

  1. You have the main goroutine accesssing m1, and the one you start in it also accesses m1. The main goroutine accesses it after the other goroutine has been launched, so they are concurrent.
  2. The main goroutine writes m1 in line #17: m1 = m2.
  3. The accesses are not synchronized, you use no mutex or channels or anything like that (sleeping is not synchronization).

Therefore it's a data race.

The obvious data race is between lines #11 reading m1, and line #17 writing m1.

But! Since line #17 assigns m2 to m1, then when/if the launched goroutine continues to run, it attempts to read m1 which may now be the value of m2 because we assigned m2 to m1. What does this mean? This introduces another data race writing m2 and reading m1.

That is after line #17 if the program does not end immediately (it may, but not necessarily), then the launched goroutine attempts to read from m1 which is now m2 which was last written in line #16, so this explains the "conflict" between lines #11 and #16.

The full go run -race output is as follows:

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

答案2

得分: 1

数据竞争

数据竞争发生在两个goroutine同时访问同一个变量,并且至少有一个访问是写操作的情况下。

指令重排序

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

m2["hello"] = 3
m1 = m2

可以被重新排序为

m1 = m2
m2["hello"] = 3

这不会改变主例程的行为,因此竞争检查也会考虑这种情况来评估竞争条件。现在,m2["hello"] = 3引发了竞争条件,并且它会打印出原始行号。

英文:

Data race

A data race occurs when two goroutines access the same variable concurrently and at least one of the accesses is a write.

Instructions Reorder

compilers and processors may reorder the reads and writes executed within a single goroutine as far as the reordering does not change the behaviour within the routine, it doesn' ensure behaviour of other goroutines to be unaffected

m2[&quot;hello&quot;] = 3
m1 = m2

Can be reordered to

m1 = m2
m2[&quot;hello&quot;] = 3

That won't alter the behaviour of the main routine and thus race checking will consider that also for evaluating race condition. Now we have m2[&quot;hello&quot;] = 3 causing the race condition and it prints out the same with its original line number

huangapple
  • 本文由 发表于 2017年9月15日 15:07:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/46233680.html
匿名

发表评论

匿名网友

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

确定