Go 数据竞争检测器 #
数据竞争是并发系统中最常见,同时也最难处理的 Bug 类型之一。数据竞争会在两个 goroutine 并发访问同一个变量,且至少有一个访问为写入时产生。
下面是一个会导致程序崩溃的例子:
package main
import "fmt"
func main() {
c := make(chan bool)
m := make(map[string]string)
go func() {
m["1"] = "a" // 第一个冲突的访问
c <- true
}()
m["2"] = "b" // 第二个冲突的访问
<-c
for k, v := range m {
fmt.Println(k, v)
}
}
运行 go run -race ./main.go
程序会马上崩溃:
==================
WARNING: DATA RACE
Write at 0x00c00010a090 by goroutine 7:
runtime.mapassign_faststr()
/usr/local/go/src/runtime/map_faststr.go:203 +0x0
main.main.func1()
/root/workspace/main.go:9 +0x4a
Previous write at 0x00c00010a090 by main goroutine:
runtime.mapassign_faststr()
/usr/local/go/src/runtime/map_faststr.go:203 +0x0
main.main()
/root/workspace/main.go:12 +0x108
Goroutine 7 (running) created at:
main.main()
/root/workspace/main.go:8 +0xeb
==================
2 b
1 a
Found 1 data race(s)
数据竞争检测器 #
Go 内置了数据竞争检测器。使用时将 -race
标记添加到 go 命令之后:
go test -race mypkg // 测试该包
go run -race mysrc.go // 运行其源文件
go build -race mycmd // 构建该命令
go install -race mypkg // 安装该包
选项 #
Go 提供的 GORACE
环境变量可以用来设置竞争检测器的选项,格式为 GORACE="option1=val1 option2=val2"
支持的选项:
log_path
(默认为stderr
):竞争检测器会将报告写入名为<log_path>.pid
的文件中。如果值为stdout
或stderr
时会将报告分别写入到标准输出和标准错误中。exitcode
(默认为 66):检测到竞争后使用的退出状态码。strip_path_prefix
(默认为 “"):从所有报告文件的路径中去除此前缀,使报告更加简洁。history_size
(默认为 1):每个 Go 程序的内存访问历史为32K * 2**history_size
个元素。增加该值可以在报告中避免 “failed to restore the stack” 的提示,但代价是会增加内存的使用。halt_on_error
(默认为 0):控制程序在报告第一次数据竞争后是否退出。
例如:
GORACE="log_path=/tmp/race/report strip_path_prefix=/my/go/sources/" go test -race
编译标签 #
如果某些代码不需要被竞争检测器检查,可以通过编译标签来排除:
//go:build !race
package foo
运行时开销 #
竞争检测器只会寻找在运行时发生的竞争,因此它不能在未执行的代码路径中寻找竞争。如果你的测试覆盖率比较低,可以通过 go build -race
来编译,来寻找更多的竞争。
竞争检测的代价因程序而异,但对于典型的程序,内存的使用会增加 5 到 10 倍, 而执行时间会增加 2 到 20 倍。