golang 中的 race

基本就是抄自 golang blog

背景

数据竞争是所有并发程序都需要注意的问题,尤其是像 golang 这种原生支持并发的程序.
在 golang 发行到 1.1 版本时自带 race detector, 用来找到 go 代码中的数据竞争状态。

race detector 基于 google 开源的 C/C++ 库 ThreadSanitizer runtime library, 在集成到 golang 中后,就发现了标准库中 42 处竞争状态。

工作原理

race detector 集成到了 golang 的工具链中。在命令行使用 -race 选项,编译器就会记录代码中所有的 memory access, 包括访问方式和时间。运行时库观察所有对共享变量的未同步访问,每当发现这样的行为,就会输出一个 warning, 具体实现细节可见 链接

因为依赖于运行时库的设计,race detector 只能在运行时做数据竞争状态的检查。但是在开启 race detector 后,cpu 的负载将为原来不启用 race detector 的 10 倍。所以不可能在线上应用中启用 race detector. 但是在生产环境中使用的一种情况是,在许多线上的 servers 中,只有一个 server 的应用启用了 race detector, 对业务影响不大。

使用 race detector

race detector 集成到了 golang 中的工具链中,只要命令行中设置 -race 选项就可以使用 race detector

1
2
3
4
go test -race
go run -race
go build
go install -race pkg

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
start := time.Now()
var t *time.Timer
t = time.AfterFunc(randomDuration(), func() {
fmt.Println(time.Now().Sub(start))
t.Reset(randomDuration())
})
time.Sleep(5 * time.Second)
}

func randomDurtion() time.Duration {
return time.Duration(rand.Int63n(1e9))
}

在这段代码看不出来有什么数据竞争带代码,运行起来也似乎没什么问题,但是在极偶尔的情况下第 6 行代码会出现 nil 指针引用的情况,打开 race detector 发现在 4 行代码的调用 time.sleep 会开启一个 goroutine 来执行传入的 func, func 闭包对 main 中的 t 进行了读写,但是 t 的赋值是在 main 中完成的,不一定是在 goroutine 开启之前,所以可能会发生 nil 指针引用的情况。
这段代码要修正把对 t 的读写全部放在 main 中就好。