因为并发 bug 具有不确定性、不可复现性和对时序的敏感性,传统"跑一遍断言结果"的测试方式很难抓住它们。
展开解析:
- 不可复现:竞态、死锁是否出现取决于线程调度的具体交错顺序,同样代码跑一千次可能只失败一次,测试通过不代表没有 bug。
- 测试本身改变行为:加日志、断点、sleep 会改变时序,bug 一观察就消失(heisenbug)。
- 状态空间爆炸:n 个线程的交错执行路径数是天文数字,无法穷举;单元测试只能覆盖其中极少几种交错。
- 环境敏感:CPU 核数、负载、操作系统调度策略不同,本地通过、CI 或生产才翻车。
应对手段:
- 压力测试:高并发、长时间、多核环境反复跑,提高暴露概率。
- 注入时序扰动:用工具强制不同调度(如 Java 的 jcstress、Go 的
-race数据竞争检测、TSan)。 - 模型检验/形式化:对关键并发协议用 TLA+ 等验证。
- 设计上规避:缩小共享状态、用不可变数据和成熟并发原语,让代码"不需要测竞态"。
追问方向:数据竞争检测的原理、如何写出可测的并发代码(依赖注入时钟/调度器)。