代码触发Thread Sanitizer警告:是否为误报?求排查建议
问题分析与解决方案
代码示例
#include <thread> #include <condition_variable> #include <mutex> int main() { std::mutex mutex; bool done = false; std::condition_variable condvar; std::jthread thread([&](std::stop_token stoken) { std::this_thread::sleep_for(std::chrono::seconds(2)); { std::unique_lock lock(mutex); done = true; } condvar.notify_all(); while (!stoken.stop_requested()) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); } }); std::unique_lock lock(mutex); condvar.wait_for(lock, std::chrono::seconds(60), [&]{ return done; }); thread.request_stop(); }
编译命令
g++ main.cc -std=gnu++20 -fsanitize=thread
Thread Sanitizer警告分析
1. 重复锁警告(double lock)
================== WARNING: ThreadSanitizer: double lock of a mutex (pid=2103) #0 pthread_mutex_lock ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:4240 (libtsan.so.0+0x53908) #1 __gthread_mutex_lock(pthread_mutex_t*) <null> (a.out+0x34c5) #2 std::mutex::lock() <null> (a.out+0x354e) #3 std::unique_lock<std::mutex>::lock() <null> (a.out+0x5b1f) #4 std::unique_lock<std::mutex>::unique_lock(std::mutex&) <null> (a.out+0x551c) #5 main::{lambda(std::stop_token)#1}::operator()(std::stop_token) const <null> (a.out+0x2649) #6 void std::__invoke_impl<void, main::{lambda(std::stop_token)#1}, std::stop_token>(std::__invoke_other, main::{lambda(std::stop_token)#1}&&, std::stop_token&&) <null> (a.out+0x3380) #7 std::__invoke_result<main::{lambda(std::stop_token)#1}, std::stop_token>::type std::__invoke<main::{lambda(std::stop_token)#1}, std::stop_token>(main::{lambda(std::stop_token)#1}&&, std::stop_token&&) <null> (a.out+0x32bc) #8 void std::thread::_Invoker<std::tuple<main::{lambda(std::stop_token)#1}, std::stop_token> >::_M_invoke<0ul, 1ul>(std::_Index_tuple<0ul, 1ul>) <null> (a.out+0x31c8) #9 std::thread::_Invoker<std::tuple<main::{lambda(std::stop_token)#1}, std::stop_token> >::operator()() <null> (a.out+0x3154) #10 std::thread::_State_impl<std::thread::_Invoker<std::tuple<main::{lambda(std::stop_token)#1}, std::stop_token> > >::_M_run() <null> (a.out+0x310a) #11 <null> <null> (libstdc++.so.6+0xdc252) Location is stack of main thread. Location is global '<null>' at 0x000000000000 ([stack]+0x00000001e2e0) Mutex M9 (0x7ffe4c7db2e0) created at: #0 pthread_mutex_lock ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:4240 (libtsan.so.0+0x53908) #1 __gthread_mutex_lock(pthread_mutex_t*) <null> (a.out+0x34c5) #2 std::mutex::lock() <null> (a.out+0x354e) #3 std::unique_lock<std::mutex>::lock() <null> (a.out+0x5b1f) #4 std::unique_lock<std::mutex>::unique_lock(std::mutex&) <null> (a.out+0x551c) #5 main <null> (a.out+0x285f) SUMMARY: ThreadSanitizer: double lock of a mutex (/mnt/c/Users/d7d1c/Documents/Linux/test/a.out+0x34c5) in __gthread_mutex_lock(pthread_mutex_t*) ==================
这是误报。代码中主线程和子线程分别持有独立的std::unique_lock实例,对同一个std::mutex的加锁操作来自不同线程,不存在同一线程重复加锁的逻辑。误报原因大概率是TSAN对栈上分配的std::mutex跟踪存在偏差,或是g++ 11版本中libstdc++与TSAN的兼容性问题。
2. 数据竞争警告(data race)
================== WARNING: ThreadSanitizer: data race (pid=2103) Write of size 1 at 0x7ffe4c7db2a3 by thread T1 (mutexes: write M9): #0 main::{lambda(std::stop_token)#1}::operator()(std::stop_token) const <null> (a.out+0x2669) #1 void std::__invoke_impl<void, main::{lambda(std::stop_token)#1}, std::stop_token>(std::__invoke_other, main::{lambda(std::stop_token)#1}&&, std::stop_token&&) <null> (a.out+0x3380) #2 std::__invoke_result<main::{lambda(std::stop_token)#1}, std::stop_token>::type std::__invoke<main::{lambda(std::stop_token)#1}, std::stop_token>(main::{lambda(std::stop_token)#1}&&, std::stop_token&&) <null> (a.out+0x32bc) #3 void std::thread::_Invoker<std::tuple<main::{lambda(std::stop_token)#1}, std::stop_token> >::_M_invoke<0ul, 1ul>(std::_Index_tuple<0ul, 1ul>) <null> (a.out+0x31c8) #4 std::thread::_Invoker<std::tuple<main::{lambda(std::stop_token)#1}, std::stop_token> >::operator()() <null> (a.out+0x3154) #5 std::thread::_State_impl<std::thread::_Invoker<std::tuple<main::{lambda(std::stop_token)#1}, std::stop_token> > >::_M_run() <null> (a.out+0x310a) #6 <null> <null> (libstdc++.so.6+0xdc252) Previous read of size 1 at 0x7ffe4c7db2a3 by main thread (mutexes: write M9): #0 main::{lambda()#2}::operator()() const <null> (a.out+0x2741) #1 bool std::condition_variable::wait_until<std::chrono::_V2::steady_clock, std::chrono::duration<long, std::ratio<1l, 1000000000l> >, main::{lambda()#2}>(std::unique_lock<std::mutex>&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long, std::ratio<1l, 1000000000l> > > const&, main::{lambda()#2}) <null> (a.out+0x2c09) #2 bool std::condition_variable::wait_for<long, std::ratio<1l, 1l>, main::{lambda()#2}>(std::unique_lock<std::mutex>&, std::chrono::duration<long, std::ratio<1l, 1l> > const&, main::{lambda()#2}) <null> (a.out+0x2a93) #3 main <null> (a.out+0x28a9) As if synchronized via sleep: #0 nanosleep ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:366 (libtsan.so.0+0x6696c) #1 void std::this_thread::sleep_for<long, std::ratio<1l, 1l> >(std::chrono::duration<long, std::ratio<1l, 1l> > const&) <null> (a.out+0x5450) #2 main::{lambda(std::stop_token)#1}::operator()(std::stop_token) const <null> (a.out+0x2627) #3 void std::__invoke_impl<void, main::{lambda(std::stop_token)#1}, std::stop_token>(std::__invoke_other, main::{lambda(std::stop_token)#1}&&, std::stop_token&&) <null> (a.out+0x3380) #4 std::__invoke_result<main::{lambda(std::stop_token)#1}, std::stop_token>::type std::__invoke<main::{lambda(std::stop_token)#1}, std::stop_token>(main::{lambda(std::stop_token)#1}&&, std::stop_token&&) <null> (a.out+0x32bc) #5 void std::thread::_Invoker<std::tuple<main::{lambda(std::stop_token)#1}, std::stop_token> >::_M_invoke<0ul, 1ul>(std::_Index_tuple<0ul, 1ul>) <null> (a.out+0x31c8) #6 std::thread::_Invoker<std::tuple<main::{lambda(std::stop_token)#1}, std::stop_token> >::operator()() <null> (a.out+0x3154) #7 std::thread::_State_impl<std::thread::_Invoker<std::tuple<main::{lambda(std::stop_token)#1}, std::stop_token> > >::_M_run() <null> (a.out+0x310a) #8 <null> <null> (libstdc++.so.6+0xdc252) Location is stack of main thread. Location is global '<null>' at 0x000000000000 ([stack]+0x00000001e2a3) Mutex M9 (0x7ffe4c7db2e0) created at: #0 pthread_mutex_lock ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:4240 (libtsan.so.0+0x53908) #1 __gthread_mutex_lock(pthread_mutex_t*) <null> (a.out+0x34c5) #2 std::mutex::lock() <null> (a.out+0x354e) #3 std::unique_lock<std::mutex>::lock() <null> (a.out+0x5b1f) #4 std::unique_lock<std::mutex>::unique_lock(std::mutex&) <null> (a.out+0x551c) #5 main <null> (a.out+0x285f) Thread T1 (tid=2105, running) created by main thread at: #0 pthread_create ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:969 (libtsan.so.0+0x605b8) #1 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) <null> (libstdc++.so.6+0xdc328) #2 std::thread std::jthread::_S_create<main::{lambda(std::stop_token)#1}>(std::stop_source&, main::{lambda(std::stop_token)#1}&&) <null> (a.out+0x2b43) #3 std::jthread::jthread<main::{lambda(std::stop_token)#1}, , void>(main::{lambda(std::stop_token)#1}&&) <null> (a.out+0x29bb) #4 main <null> (a.out+0x2849) SUMMARY: ThreadSanitizer: data race (/mnt/c/Users/d7d1c/Documents/Linux/test/a.out+0x2669) in main::{lambda(std::stop_token)#1}::operator()(std::stop_token) const ==================
这同样是误报。共享变量done的读写均受std::mutex严格保护:
- 子线程写入
done时,持有unique_lock; - 主线程在
wait_for的谓词中读取done时,wait_for会自动重新获取锁后执行谓词逻辑。
两者的访问不存在未同步的竞争场景。
解决方案建议
针对大型项目中的类似问题,可采取以下措施:
- 迁移共享变量到堆:将
std::mutex和done从栈内存移到堆(比如用std::make_shared包裹),避免TSAN对栈变量的跟踪误判; - 升级编译器版本:g++ 11的TSAN兼容性存在已知问题,升级到g++ 12或更高版本可修复部分误报;
- 替换为原子变量:对于简单的标志变量(如
done),可直接使用std::atomic<bool>替代普通bool,无需手动加锁,从根源避免竞争问题; - 结合其他工具排查:除TSAN外,可使用valgrind、Helgrind等工具交叉验证,或通过日志、断点手动梳理线程同步逻辑;
- 规范同步代码:确保所有共享资源的访问都严格遵循同步规则,避免隐式的未同步操作(如捕获栈变量到线程时,需保证变量生命周期覆盖线程运行周期)。
内容的提问来源于stack exchange,提问作者d7d1cd
相关产品推荐
相关产品推荐

