问题描述
最近我学会了-race
检查 go 中是否存在比赛条件的选项。完整的命令是go run -race xxx.go
它真的对我帮助很大。但是与下面的代码一样,我认为检查结果是错误的,并尝试了很多方法(我尝试在下面引起恐慌(以获得真正的恐慌,但失败了。所以我想知道代码是否正确,种族检查是否错误,或者你能修改我的代码,以便我可以看到真正的恐慌。多谢。
代码
package main
import "fmt"
type myType struct {
A int
}
func main(){
c:=make(chan bool)
x := new(myType)
go func(){
x = new(myType) // write to x
c <- true
}()
_ = *x // read from x
<-c
fmt.Println("end")
}
比赛检查结果
go run -race test.go
==================
WARNING: DATA RACE
Write at 0x00c00009c010 by goroutine 6:
main.main.func1()
/Users/yaodongen/test.go:12 +0x56
Previous read at 0x00c00009c010 by main goroutine:
main.main()
/Users/yaodongen/test.go:15 +0xe2
Goroutine 6 (running) created at:
main.main()
/Users/yaodongen/test.go:11 +0xd4
==================
end
Found 1 data race(s)
exit status 66
我的观点
我试图找到竞争条件报告的原因。在一篇文章(中文(中,它提到操作a = in64(0)
不是原子的。例如,在一台 32 位机器中,像 int64 这样的数据可能是 64 位长度,CPU 可能会复制一半的数据并被其他人中断。在下面的代码中(证明 golang 副本不是原子的(,我写了一个代码来证明它的真实性。但就我而言,代码x = new(myType)
是复制指针值,我认为它可以在一个 CPU 副本中完成。换句话说,该操作是原子操作,永远不会达到争用条件。
证明 golang 副本不是原子的
package main
import "fmt"
import "time"
func main(){
var x = [...]int{1,1,1,1,1,1}
c := make(chan int, 100)
go func(){
for i:=0;;i++{
if i&1 == 0 {
x = [...]int{2,2,2,2,2,2} // write to x
}else{
x = [...]int{1,1,1,1,1,1} // write to x
}
c<-0 // let other goroutine see the change of x
<-c
}
}()
go func(){
for {
d := x // read from x
if d[0] != d[5] {
fmt.Println(d)
panic("error") // proved the copy operation is not atomic
}
c<-0
<-c
}
}()
time.Sleep(time.Millisecond * 10000)
fmt.Println("end")
}
我试图引起恐慌
但是它失败了,如果存在竞争条件(错误的内存地址(,代码将崩溃。
package main
import "fmt"
import "time"
type myType struct {
A int
}
func main(){
x := new(myType)
c := make(chan int, 100)
go func(){
for {
x = new(myType) // write to x
c<-0
<-c
}
}()
for i:=0; i<4; i++{
go func(){
for {
_ = *x // if exists a race condition, `*x` will visit a wrong memory address, and will panic
c<-0
<-c
}
}()
}
time.Sleep(time.Second * 10)
fmt.Println("end")
}
Go 的种族检测从不给出误报。如果它告诉你有一场比赛,那么就有一场比赛。它可能无法识别所有种族(它们必须碰巧是可检测到的(,但它发现的总是积极的(种族检测器中的错误不计算在内(。
示例中的争用条件清晰而简单。你有 2 个 goroutine,一个读取变量,另一个在不同步的情况下写入它。这是竞争条件的配方。
争用条件使应用不可预测。争用条件的行为未定义。任何经历过的行为都属于不确定的,包括缺乏恐慌。不要诱惑魔鬼,如果有竞争条件,请使用适当的同步。故事结束。
请参阅在没有锁的情况下同时读取函数指针是否安全?