ThreadSanitizer(TSan)是Clang/GCC提供的动态数据竞争检测工具,通过插桩内存访问并跟踪线程与锁状态,在运行时发现未同步的跨线程共享变量读写冲突;启用需编译、链接、运行三步配合,使用-fsanitize=thread且不可与ASan/MSan混用,须保留-g和-O1/O2优化,正确标注自定义同步原语,结合TSAN_OPTIONS调试并集成至CI流程。
ThreadSanitizer(TSan)是 Clang 和 GCC 提供的动态数据竞争检测工具,专为 C/C++ 多线程程序设计。它通过插桩内存访问指令、跟踪线程与锁状态,在运行时实时发现未受同步保护的**跨线程共享变量读写冲突**——也就是典型的数据竞争(data race)。启用简单,但需注意编译、链接和运行三步配合,否则可能漏报或报错。
这是启用 TSan 的核心开关。必须同时作用于所有源文件(包括头文件中内联函数),且不能与 AddressSanitizer(ASan)或 MemorySanitizer(MSan)混用(TSan 有自己的内存模型)。
TSan 对同步原语敏感,但不是所有同步都被自动识别。以下情况容易出问题:
__atomic_thread_fence)或 C++20 std::atomic_ref,TSan 默认不理解其同步语义,需用 __tsan_acquire/__tsan_release 等内置函数显式标记-fno-threadsafe-statics 测试PTHREAD_MUTEX_INITIALIZER 是安全的;但若用 pthread_mutex_init(&m, nullptr) 后忘记检查返回值,且初始化失败,TSan 可能误判为未初始化锁一旦触发数据竞争,TSan 会打印类似下面的结构化报告:
WARNING: ThreadSanitizer: data race Read of size 4 at 0x7b0c0000f010 by thread T1: #0 main.cpp:15 in inc_counter() Previous write of size 4 at 0x7b0c0000f010 by thread T2: #0 main.cpp:12 in dec_counter() Location is global 'counter' at main.cpp:5:12
关键信息有三块:冲突类型(Read/Write)、地址、线程 ID、调用栈、变量名及位置。注意“Previous”不一定是时间上最早的操作,而是 TSan 认为构成竞争关系的另一方访问。
mutex (0x...) 在堆栈中缺失,说明某处没加锁或锁没覆盖到该访问std::thread::_State_impl<...>::_M_run(),说明竞争发生在 lambda 或可调用对象里,需回溯到启动线程的那行代码TSAN_OPTIONS="halt_on_error=1" 可让程序在首次报错时中断,方便用 gdb 调试TSan 不适合上线运行(性能开销约 5–10 倍,内存占用翻倍),但非常适合作为 CI 中的专项检查项:
add_compile_options(-fsanitize=thread) 并单独构建 test_tsan 目标TSAN_OPTIONS="suppressions=tsan.supp" 指定抑制文件,把已知第三方库或暂时无法修复的竞争临时屏蔽(格式见 TSan 官方文档)-fsanitize=undefined 一起用(需分开编译):UBSan 查未定义行为,TSan 查并发错误,二者互补基本上就这些。TSan 不复杂但容易忽略细节,只要编译对、跑起来、读得懂报告,90% 的隐蔽数据竞争都能被揪出来。