数据竞争是并发程序中最常见的,也是最难发现的并发问题,所幸的是,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()
}text
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以及数据的创建、读/写信息都显示出来了,很方便我们分析数据竞争是怎么产生的。
如果你不想对某些函数进行数据竞争的检查,则可以使用条件编译,在文件的第一行加上以下一行:
text
// +build !race当然,现在的Go版本使用新的条件编译语法:
text
//go:build !race如果你想兼容以前的Go版本,这两种写法就都加上:
text
//go:build !race
// +build !race注意,Go内建的数据竞争检测器并不会执行静态分析,而是在运行时对内存访问进行检查,只有针对运行的代码才有可能发现数据竞争问题,对于未被访问的代码,是发现不了数据竞争问题的。
所以在测试时并不能发现全部的数据竞争问题。通过运行开启了数据竞争检测器的编译好的二进制程序,是有可能发现更多的数据竞争问题的,但是也不能做到百分之百地发现,因为有些数据竞争可能只有在特定的条件下才会发生,而这个特定的条件什么时候存在并不能确定。另外,不要在生产环境中运行开启了数据竞争检测器的程序,因为进行数据竞争检查是有代价的。在开启了数据竞争检测器的情况下,内存占用可能增加5~10倍,而运行时间可能增加2~20倍。