← 中文资源

简体中文版本

Go — 检测到数据竞态

读懂 Go 竞态报告,找出冲突访问,并用互斥锁、channel 或原子操作修复同步,而不是忽略警告。

阅读英文原文

报告是地图,不是噪音

竞态报告显示对同一内存的两次访问之间没有 happens-before 关系。一个栈是当前读或写,另一个是此前冲突的访问。两个都要读。如果一侧持锁而另一侧没有,说明锁并没有保护那条路径。

WARNING: DATA RACE
Read at 0x00c000... by goroutine 7:
  main.(*Counter).Value()
Previous write at 0x00c000... by goroutine 6:
  main.(*Counter).Add()

选择同步原语

互斥锁保护临界区;channel 转移所有权,让共享关系显式;原子操作适合简单计数器和标志。不要为了“更快”把互斥锁和非同步读混用。对 map,要么每次访问都用同一把锁,要么使用并发安全结构。对循环变量,把值传入 goroutine,或为每次迭代创建变量。

复现并保持修复

对失败的测试运行 go test -race -count=100。竞态可能很罕见,重复和高并发能提高检出概率。在 CI 中保留启用竞态的测试套件,至少覆盖有共享状态的包。如果无法复现,记录可疑路径并添加压力测试,而不是关闭问题。

修复与验证

用正确的同步原语保护每个冲突访问,然后在高并发下重复运行竞态检测器和压力测试。确认报告消失、行为不变、吞吐没有因锁粒度过粗而明显下降。把复现用例保留为回归测试;竞态修复后再次出现,通常意味着还有另一条未保护的路径。