英文:
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行会导致数据竞争,m1和m2是不同的变量?
我的Go版本是1.8。我猜测这是一种编译优化,有人可以告诉我更多信息吗?非常感谢。
英文:
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 }
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
满足数据竞争的要求有:
- 多个 goroutine 同时访问相同的资源(例如变量)。
- 这些访问中至少有一个是写操作。
- 这些访问没有进行同步。
在你的代码中满足了这三个要求:
- 你有主 goroutine 访问
m1,并且在其中启动的 goroutine 也访问m1。主 goroutine 在另一个 goroutine 启动之后访问它,所以它们是并发的。 - 主 goroutine 在第 17 行写入了
m1:m1 = m2。 - 这些访问没有进行同步,你没有使用互斥锁、通道或其他同步机制(睡眠不是同步操作)。
因此,这是一个数据竞争。
明显的数据竞争发生在第 11 行读取 m1 和第 17 行写入 m1 之间。
但是!由于第 17 行将 m2 赋值给了 m1,那么当/如果启动的 goroutine 继续运行时,它会尝试读取 m1,而此时 m1 可能是 m2 的值,因为我们将 m2 赋值给了 m1。这意味着什么?这引入了另一个数据竞争,写入 m2 并读取 m1。
这就是在第 17 行之后,如果程序不立即结束(可能会,但不一定),则启动的 goroutine 尝试从 m1 读取的原因,而此时 m1 是 m2,它是在第 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:
- Multiple goroutines accessing the same resource (e.g. a variable) concurrently.
- At least one of those accesses is a write.
- The accesses are uncynchronized.
In your code all 3 requirements are met:
- You have the main goroutine accesssing
m1, and the one you start in it also accessesm1. The main goroutine accesses it after the other goroutine has been launched, so they are concurrent. - The main goroutine writes
m1in line #17:m1 = m2. - 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["hello"] = 3
m1 = m2
Can be reordered to
m1 = m2
m2["hello"] = 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["hello"] = 3 causing the race condition and it prints out the same with its original line number
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论