英文:
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
m1
in 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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论