为何ThreadSanitizer仅报告单个数据竞争?测试程序含两处竞态
问题:ThreadSanitizer仅报告一处数据竞争的疑问
我编写了一个故意包含两处数据竞争的测试程序,但无论如何修改,ThreadSanitizer(TSAN)始终仅报告第一处竞态。请问TSAN是否只会报告单个Bug?是否存在Bug被其他Bug遮挡导致无法检测的情况?这与我的程序是否有关?
测试程序代码
#include <thread> #include <mutex> #include <atomic> #include <iostream> #include <unistd.h> std::atomic<bool> stop{false}; std::mutex main_t1_mutex; std::mutex t1_t2_mutex; // fully useless by intention // global vars accessed by main and threads int i1; int i2; void func_t1(int* retval) { int internal = 0; //while( !stop ) for ( unsigned int tt = 0; tt < 1000; tt++ ) { //main_t1_mutex.lock(); t1_t2_mutex.lock(); internal+=i1; t1_t2_mutex.unlock(); //main_t1_mutex.unlock(); usleep(1*100); } *retval=internal; } void func_t2(int* retval) { int internal = 0; //while( !stop ) for ( unsigned int tt = 0; tt < 1000; tt++ ) { // this mutex will never protect against main thread. Intended for testing valgrind/tsan t1_t2_mutex.lock(); internal+=i2; t1_t2_mutex.unlock(); usleep(1*100); } *retval = internal; } int main() { std::cout << "Hallo" << std::endl; int retval1; int retval2; std::thread t1( func_t1, &retval1 ); std::thread t2( func_t2, &retval2 ); for ( unsigned int i = 0; i < 10; i++ ) { usleep(1*1000); main_t1_mutex.lock(); i1 = i; i2 = i; main_t1_mutex.unlock(); std::cout << "step " << i << std::endl; } //stop=true; t1.join(); t2.join(); std::cout << "Value 1: " << retval1 << std::endl; std::cout << "Value 2: " << retval2 << std::endl; }
解答
TSAN不是只会报告单个数据竞争,它具备检测并报告多个竞态条件的能力,但确实存在部分竞态因未被实际触发而无法被检测到的情况,这和你的程序逻辑直接相关。
你的程序中的两处竞态分析
- 第一处(已被检测):
i1的访问冲突。主线程用main_t1_mutex保护i1的写操作,但t1线程用t1_t2_mutex读取i1——两个线程使用的是不同互斥锁,完全无法同步,因此必然存在数据竞争,TSAN能稳定捕捉到这个冲突。 - 第二处(未被检测):
i2的访问冲突。主线程用main_t1_mutex写i2,t2线程用t1_t2_mutex读i2,同样是不同锁的无同步访问,理论上也是数据竞争,但TSAN没报告的原因在于:- 竞态触发概率低:主线程仅循环10次,每次休眠1秒;而
t2线程循环1000次,每次休眠0.1秒。两者的访问时机交叉概率极低,TSAN需要捕捉到实际发生的线程间数据访问冲突才能报告,若运行过程中没产生足够的交叉操作,就不会被检测到。 - 线程调度随机性:不同运行环境下的线程调度顺序不同,可能某次运行能触发第二处竞态,某次则不行。
- TSAN的动态检测特性:TSAN是动态工具,依赖程序运行时的实际执行轨迹,而非静态分析。如果某个竞态在本次运行中没有实际发生,就不会被记录。
- 竞态触发概率低:主线程仅循环10次,每次休眠1秒;而
让TSAN检测到第二处竞态的调整方案
- 缩短主线程的休眠时间,比如把
usleep(1*1000)改为usleep(100),提高写i2的频率,增加和t2线程读操作的交叉概率。 - 移除所有
usleep调用,让线程尽可能密集地访问共享变量,大幅提升竞态触发的可能性。 - 增加主线程的循环次数,比如从10改为1000,延长写操作的持续时间,覆盖
t2线程的整个执行周期。
补充说明
TSAN默认会在检测到第一个数据竞争后继续运行(除非你通过halt_on_error=1参数强制终止),所以它并非只报告单个Bug,只是你的程序中第二处竞态未被实际触发而已。
内容的提问来源于stack exchange,提问作者Klaus
相关产品推荐
相关产品推荐

