检查Golang程序中的数据竞争

race 数据竞争是并发程序中最常见的,也是最难发现的并发问题,所幸的是,Go内置了在一定程度上可以发现竞争问题。你可以在测试数据竞争检测器(data race detector),或者运行程序时使用-race开启数据竞争检测器,或者在编译程序时开启,编译好的二进制程序在运行时也可以开启数据竞争检测:

go
go test -race mypkg // 测试mypkg包 go run -race mysrc.go // 运行时测试源文件 go build -race mycmd // 编译时测试 go install -race mypkg // 安装时测试

还是以计数器的程序(TestCounter)为例,使用-race参数后会报出“WARNING:DATA RACE”错误。

go
func TestCounter() { var counter int64 // 计数值 var wg sync.WaitGroup // 用來等待子goroutine全部执行完 for i := 0; i < 64; i++ { wg.Add(1) go func() { for i := 0; i < 1000000; i++ { // 循环100万次 counter++ // 计数值加1 } wg.Done() }() } wg.Wait() if counter != 64000000 { fmt.Printf("counter should be 64000000, but got %d\n", counter) } } func main() { TestCounter() }
go run -race main.go ================== WARNING: DATA RACE Read at 0x00c00000e238 by goroutine 8: main.TestCounter.func1() /Users/test/main.go:41 +0x40 Previous write at 0x00c00000e238 by goroutine 7: main.TestCounter.func1() /Users/test/main.go:41 +0x50 Goroutine 8 (running) created at: main.TestCounter() /Users/test/main.go:39 +0x6c main.main() /Users/test/main.go:54 +0x20 Goroutine 7 (running) created at: main.TestCounter() /Users/test/main.go:39 +0x6c main.main() /Users/test/main.go:54 +0x20 ================== ================== WARNING: DATA RACE Write at 0x00c00000e238 by goroutine 8: main.TestCounter.func1() /Users/test/main.go:41 +0x50 Previous write at 0x00c00000e238 by goroutine 7: main.TestCounter.func1() /Users/test/main.go:41 +0x50 Goroutine 8 (running) created at: main.TestCounter() /Users/test/main.go:39 +0x6c main.main() /Users/test/main.go:54 +0x20 Goroutine 7 (running) created at: main.TestCounter() /Users/test/main.go:39 +0x6c main.main() /Users/test/main.go:54 +0x20 ================== counter should be 64000000, but got 58226219 Found 2 data race(s) exit status 66

在执行的时候加上-race参数,可以看到Go数据竞争检测器发现了数据竞争的问题(WARNING:DATA RACE),并且把数据竞争的goroutine以及数据的创建、读/写信息都显示出来了,很方便我们分析数据竞争是怎么产生的。

如果你不想对某些函数进行数据竞争的检查,则可以使用条件编译,在文件的第一行加上以下一行:

// +build !race

当然,现在的Go版本使用新的条件编译语法:

//go:build !race

如果你想兼容以前的Go版本,这两种写法就都加上:

//go:build !race // +build !race

注意,Go内建的数据竞争检测器并不会执行静态分析,而是在运行时对内存访问进行检查,只有针对运行的代码才有可能发现数据竞争问题,对于未被访问的代码,是发现不了数据竞争问题的。

所以在测试时并不能发现全部的数据竞争问题。通过运行开启了数据竞争检测器的编译好的二进制程序,是有可能发现更多的数据竞争问题的,但是也不能做到百分之百地发现,因为有些数据竞争可能只有在特定的条件下才会发生,而这个特定的条件什么时候存在并不能确定。另外,不要在生产环境中运行开启了数据竞争检测器的程序,因为进行数据竞争检查是有代价的。在开启了数据竞争检测器的情况下,内存占用可能增加5~10倍,而运行时间可能增加2~20倍。