★ 数据竞争检测器 race 怎么用?原理是什么?

一句话结论:race 是 Go 官方内置的数据竞争检测器,把 -race 传给 go build / go test / go run 即可开启;它通过编译期插桩加运行时 happens-before 追踪,在“两个 goroutine 无同步地并发访问同一内存且至少一个是写”时给出详细报告,是排查并发 bug 的第一工具。

使用方法

  • 测试:go test -race ./...(最常用,建议在 CI 里常开)。
  • 运行与构建:go run -race main.go、go build -race xxx。
  • 只对“实际执行到的代码路径”有效:没跑到就不会报,所以测试要充分触发并发分支。

工作原理

  • 编译器对每个内存读写指令插入检测代码,记录访问的 goroutine 与内存地址。
  • 运行库为每个 goroutine 维护向量时钟(vector clock),用来近似 happens-before 关系。
  • 当检测到两个访问同一地址、之间无 happens-before、且至少一个为写时,判定为数据竞争并输出报告。
  • 报告包含两个冲突访问的完整 goroutine 栈、变量地址与创建点,帮你定位“谁在写、谁在读、该在哪做同步”。

开销与边界

维度说明
CPU通常慢 2~20 倍(视程序而定)
内存通常增加 5~10 倍
覆盖只覆盖执行过的代码路径
不检测死锁、逻辑错误、以及“没跑到”的竞争

race 没报告竞争,不代表程序绝对正确,只说明本次执行没观察到竞争。

最小复现

func TestRace(t *testing.T) {
	var n int
	go func() {
		n++ // 写
	}()
	n++ // 读改写,与上面的写无同步
	_ = n
}

执行 go test -race ./... 会输出 “WARNING: DATA RACE”,给出两个访问栈与 goroutine 创建位置;把一侧放进 sync.Mutex 或改用 atomic 后报告消失。

常见追问 / 记忆点

  • 追问:加了锁还是报竞争?——通常是某条路径漏加锁,或加的不是同一把锁。
  • 追问:race 能发现死锁吗?——不能,它是竞争检测器,不是死锁检测器。
  • 记忆点:竞争三要素“共享 + 并发 + 无同步”;race 只抓执行路径,CI 里常开 -race。
笔记加载中…