能,但仅在竞态实际发生时捕获——需两个goroutine真正并发读写同一内存地址,未触发的路径、冷门分支、CGO共享内存均无法检测。

go run -race 能不能直接发现数据竞争?
能,但只在竞态真正发生时才报——它不是静态扫描,而是运行时插桩监控内存访问。没跑过的并发路径、没触发的读写交错,go run -race 一概看不见。
- 必须让两个 goroutine 实际并发读写同一变量(比如一个
int字段、一个未加锁的map、一个闭包捕获的局部指针) - 测试里只写
go f()但不等它执行完、不校验结果,-race很可能沉默 - 常见漏报场景:冷门分支、压测才出现的高并发窗口、CGO 内部的共享内存访问(
-race穿不透 C 栈) - 命令示例:
go run -race main.go或go test -race -count=5 ./...(-count=5防缓存,提高触发概率)
为什么加了 -race 还不报错?
大概率是测试没真正“撞上”竞态——-race 不预测,只记录已发生的冲突。
- 现象:本地测试通过,线上偶发错值或 panic;
go test -race一次就过,但没加-count或并发数太低 - 检查点:测试里是否用了
sync.WaitGroup等待所有 goroutine 结束?是否对共享状态做了最终一致性校验(比如累加后比对总数)? - 初始化副作用容易被忽略:全局
map在init()里注册,而测试没覆盖该阶段,竞态就发生在-race启动前 - 别信“逻辑上应该有竞态”,
-race只认内存地址 + 读写动作 + goroutine ID 的实际组合
看到 DATA RACE 报告该怎么读?
重点盯三行:Previous write at、Current read at、以及末尾的 goroutine 创建栈。
Colly 是一个用于 Go 语言的快速开源爬取和爬虫框架。它适用于从简单的页面提取到异步爬虫处理大量页面集合,支持请求回调和结构化解析。
-
Previous write at 0x00c00001a240 by goroutine 6→ 谁先改了这块内存(地址要记下) -
Current read at 0x00c00001a240 by goroutine 7→ 谁紧接着读了同一块(说明没同步) - 看
created at行:通常指向TestXxx里的go func() { ... }(),确认是不是你传进去的指针被多个 goroutine 并发用了 - 示例中
main.(*Cache).Get()和main.(*Cache).Set()碰同一字段,修复就是加mu sync.Mutex,且锁粒度只包字段读写,不包日志或 HTTP 调用
检测到之后,修法选错反而更危险
不是所有共享变量都适合套 sync.RWMutex,也不是所有整数都要立刻上 atomic。
立即学习“go语言免费学习笔记(深入)”;
- 写多于 10%、或单次读操作含 JSON 解析/网络调用 → 直接用
sync.Mutex,RWMutex的读锁计数和写锁等待反而拖慢 - 结构体字段级拆锁(每个字段配一个
RWMutex)会引发 cache line 伪共享,吞吐可能下降 - 临时验证可用
atomic.LoadInt32替换int,但若字段更新带逻辑判断(如 “仅当旧值为 0 才设新值”),atomic不够用,得上Mutex - 最易忽略的一点:别急着改业务代码——先确认是不是你把一个未加锁的
*bytes.Buffer传给了多个json.Encoder,问题其实在库的并发使用方式上
竞态检测本身不难,难的是让测试真实暴露它;修复也不难,难的是选对同步原语、控好锁粒度、避开伪共享和锁升级陷阱。

















