为什么是以下代码:
package main
import (
"fmt"
"time"
)
func main() {
str := "ab"
strPtr := &str
go func() {
str2 := "cd"
strPtr = &str2
}()
time.Sleep(2 * time.Second)
fmt.Printf("current address %p\n", strPtr)
}
使用
go run -race main.go
运行时会产生数据竞争? Datarace 看起来像这样:
==================
WARNING: DATA RACE
Read at 0x00c0000b4018 by main goroutine:
main.main()
/Users/***/main/main.go:16 +0x124
Previous write at 0x00c0000b4018 by goroutine 7:
main.main.func1()
/Users/***/main/main.go:13 +0x7c
Goroutine 7 (finished) created at:
main.main()
/Users/***/main/main.go:11 +0x110
==================
current address 0xc000180000
Found 1 data race(s)
exit status 66
Datarace 据报告从第
0x00c0000b4018
行上的地址 fmt.Printf("current address %p\n", strPtr)
读取,但当时 strPtr
已经指向不同的地址 0xc000180000
!怎么来的?
即使奇迹般地出现了数据争用,我在这里所做的是分配一个指针,它基于 go 内存模型应该是原子的,通过 this SO 答案:
重新引用截至今天,Go 内存模型的 2022 年 6 月 6 日版本保证不大于机器字的内存访问是原子的。
否则,对不大于机器字的内存位置x的读取r必须观察到一些写入w,使得r不会在w之前发生,并且不存在写入w',使得w在w'和w'发生之前发生在河之前也就是说,每次读取都必须观察先前或并发写入写入的值。
这是一个数据争用,因为您有并发内存读/写操作,而没有显式同步。 Goroutine 中的指针赋值与主 Goroutine 中的指针读取是并发的。
指针大小值的内存写入是原子的这一事实与内存竞争无关。存在竞争只是因为读取操作可以看到赋值之前的指针值或其之后的值,因为内存读取和内存写入之间没有发生之前的关系。所以不太可能,但理论上可以观察到该指针的两个值。