★ 什么是数据竞争?如何检测和避免并发竞态?

结论先行

数据竞争指多个 goroutine 并发访问同一内存位置,且至少一个是写操作,彼此之间没有任何同步(锁、channel、atomic 等)建立 happens-before 关系;它属于未定义行为——结果不确定、可能崩溃,“这次没出错”不代表没有 bug。检测靠 race detector(-race),避免靠“同步访问”或“只读共享/复制代替共享”。

要点

  • 判定三要素:同一内存位置 + 至少一个写 + 无同步边;只有读读并发是安全的。
  • 为什么危险:编译器和 CPU 会重排指令、各核缓存不一致,竞态不一定复现,可能只在特定负载下爆雷。
  • 常见成因:并发读写共享 map/slice/计数器、闭包共享循环变量并修改、无锁的读-改-写、double-check 缺少同步。
  • 检测手段:go test -race 或 go run -race(基于 ThreadSanitizer,需 CGO 可用);它只报告“实际执行到的”竞态,应配合高并发测试提高覆盖。
  • 规避三板斧:① 数据只读则随便共享;② 要写就用锁/channel/atomic 建立同步边;③ 复制代替共享,或把状态收进单一 owner goroutine。
  • 同步边来源:Mutex/RWMutex 的加解锁、channel 收发、WaitGroup、atomic 操作等都构成 happens-before。
  • 工程建议:race detector 常态化跑进 CI;并发代码配压力测试;code review 盯住“谁在写、写时是否持锁”。

示例

var count int
var mu sync.Mutex

func incBad() { count++ } // 数据竞争:无同步的读写
func incOK() {
	mu.Lock()
	count++
	mu.Unlock()
}

// 用 go run -race 跑并发调用,incBad 会被报告为 DATA RACE

常见追问/记忆点

  • 追问:go vet 能查出数据竞争吗?答:不能,vet 只做静态分析;竞态必须靠 -race 动态检测。
  • 追问:atomic 能解决所有竞态吗?答:单变量可以;多个变量之间的一致性仍需锁或严格设计。
  • 追问:channel 能避免竞态吗?答:能,收发构成同步边;把数据“传递”而非“共享”就是 channel 的经典用法。
  • 记忆:同址 + 一写 + 无同步 = 竞态;-race 常开,锁与 channel 是同步边的来源。
笔记加载中…