ThreadSanitizer 能查到什么和查不到什么

ThreadSanitizer 适合找没有同步保护的并发读写,但它不是“程序并发安全”的证明。它依赖运行时观测到的执行路径,没跑到的分支看不见,工具不知道的同步协议也可能被误判或漏掉。

数据竞争具体指什么

两个执行单元同时访问同一内存位置,其中至少一个写入,且两次访问之间没有有效的 happens-before 关系,就构成数据竞争。竞态条件范围更大,例如两个受锁保护的操作顺序不同也可能业务出错,却未必被工具报告。

这个差别很重要。看到“没有 race”只能说明这次运行没有观测到已知形式的无同步访问。

工具如何建立证据

编译器会给读写、锁、线程创建和销毁插入检测点。运行时维护每个线程的逻辑时钟与内存访问历史,当两次冲突访问没有同步关系时,报告两条堆栈。报告的价值在于把写入者、读取者和中间缺失的同步一起交出来。

检测会增加运行时间和内存使用,因此适合测试环境、压力测试和专门的回归任务,不适合直接替代生产监控。

C 和 Go 的使用重点不同

C 或 C++ 项目要用支持检测的编译选项重新构建依赖,混入未插桩库会留下盲区。手写原子操作、信号处理和自定义锁尤其需要确认工具是否理解。

Go 自带 race 模式,适合把单元测试、接口测试和并发压力场景各跑一遍。不要只执行最短的测试集,许多竞争只在取消、超时、缓存失效或连接复用时出现。

一次排查怎样开始

  1. 先缩小到能稳定触发的测试或请求序列。
  2. 阅读报告中的两条访问栈,确认它们共享的是同一对象。
  3. 找到对象的所有权,决定使用互斥锁、通道、原子变量或单线程归属。
  4. 修复后重复执行,并补一个覆盖原触发条件的回归测试。

锁不是唯一答案。若对象本应只由一个 goroutine 或工作线程修改,把写入集中到拥有者往往比到处加锁更容易维护。

容易被忽略的盲区

随机调度、测试时间太短、外部库未插桩和硬件相关代码都会造成漏报。原子变量也只保护单个值的读写,不能自动维护多个字段的一致性。对涉及多个状态的逻辑,仍要写清不变量并在测试里验证。

常见问题

报告一定是真问题吗

大多数报告都值得先按真实问题调查。自定义同步或工具限制可能造成误报,但应当用可解释的同步关系证明它安全。

关闭检测能提升线上性能吗

检测本来就应留在测试构建。线上性能问题要用独立的性能分析工具处理。