这是一个使用 std::condition_variable 的简单示例。使用 clang+tsan 构建以下代码时,
#include <cassert>
#include <chrono>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>
class WaitSignalLock {
private:
std::condition_variable conditionVariable;
std::mutex mtx;
bool condition = false;
public:
bool wait(const std::chrono::milliseconds& timeout);
void signal();
private:
bool conditionMet() const;
bool waitForConditionVariableSignal(std::unique_lock<std::mutex>& lck, const std::chrono::milliseconds& timeout);
};
bool WaitSignalLock::wait(const std::chrono::milliseconds& timeout) {
std::unique_lock<std::mutex> lck(this->mtx);
const auto result = waitForConditionVariableSignal(lck, timeout);
condition = false;
return result;
}
void WaitSignalLock::signal() {
// the variable used for checking the condition has to be modified under a lock (even if the variable is atomic),
// and the condition variable should not be notified under a lock
{
std::lock_guard<std::mutex> lck(this->mtx);
condition = true;
}
conditionVariable.notify_one();
}
bool WaitSignalLock::conditionMet() const {
// do not lock, method will be called under the lock already
return condition;
}
bool WaitSignalLock::waitForConditionVariableSignal(std::unique_lock<std::mutex>& lck, const std::chrono::milliseconds& timeout) {
assert(lck.owns_lock());
if(this->conditionVariable.wait_for(lck, timeout, [this]() {
return this->conditionMet();
})) {
return true;
}
else {
return false;
}
}
int main(int, char**) {
WaitSignalLock waitSignalLock;
std::thread sendSignal([&waitSignalLock]() {
std::this_thread::sleep_for(std::chrono::milliseconds(500));
waitSignalLock.signal();
});
const auto result = waitSignalLock.wait(std::chrono::seconds(1));
sendSignal.join();
return !result;
}
tsan 报告了以下两个问题:
WARNING: ThreadSanitizer: double lock of a mutex (pid=16421)
#0 pthread_mutex_lock <null> (a.out+0x7e358)
#1 __gthread_mutex_lock(pthread_mutex_t*) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/x86_64-pc-linux-gnu/bits/gthr-default.h:749:12 (a.out+0xd39a6)
#2 std::mutex::lock() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/bits/std_mutex.h:100:17 (a.out+0xd45e9)
#3 std::lock_guard<std::mutex>::lock_guard(std::mutex&) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/bits/std_mutex.h:159:19 (a.out+0xd4148)
#4 WaitSignalLock::signal() /home/jae/projects/condition_variable/main.cpp:38:37 (a.out+0xd367e)
#5 main::$_1::operator()() const /home/jae/projects/condition_variable/main.cpp:67:24 (a.out+0xd3f2f)
#6 void std::__invoke_impl<void, main::$_1>(std::__invoke_other, main::$_1&&) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/bits/invoke.h:60:14 (a.out+0xd3eb1)
#7 std::__invoke_result<main::$_1>::type std::__invoke<main::$_1>(main::$_1&&) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/bits/invoke.h:95:14 (a.out+0xd3e01)
#8 void std::thread::_Invoker<std::tuple<main::$_1> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/thread:264:13 (a.out+0xd3db9)
#9 std::thread::_Invoker<std::tuple<main::$_1> >::operator()() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/thread:271:11 (a.out+0xd3d69)
#10 std::thread::_State_impl<std::thread::_Invoker<std::tuple<main::$_1> > >::_M_run() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/thread:215:13 (a.out+0xd3c9d)
#11 execute_native_thread_routine /build/gcc/src/gcc/libstdc++-v3/src/c++11/thread.cc:80:18 (libstdc++.so.6+0xcfb73)
Location is stack of main thread.
Location is global '??' at 0x7ffd19818000 ([stack]+0x00000001ec48)
Mutex M12 (0x7ffd19836c48) created at:
#0 pthread_mutex_lock <null> (a.out+0x7e358)
#1 __gthread_mutex_lock(pthread_mutex_t*) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/x86_64-pc-linux-gnu/bits/gthr-default.h:749:12 (a.out+0xd39a6)
#2 std::mutex::lock() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/bits/std_mutex.h:100:17 (a.out+0xd45e9)
#3 std::unique_lock<std::mutex>::lock() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/bits/unique_lock.h:138:17 (a.out+0xd46df)
#4 std::unique_lock<std::mutex>::unique_lock(std::mutex&) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/bits/unique_lock.h:68:2 (a.out+0xd40b3)
#5 WaitSignalLock::wait(std::chrono::duration<long, std::ratio<1l, 1000l> > const&) /home/jae/projects/condition_variable/main.cpp:28:34 (a.out+0xd3563)
#6 main /home/jae/projects/condition_variable/main.cpp:70:40 (a.out+0xd3860)
SUMMARY: ThreadSanitizer: double lock of a mutex (/home/jae/projects/condition_variable/a.out+0x7e358) in pthread_mutex_lock
==================
==================
WARNING: ThreadSanitizer: data race (pid=16421)
Write of size 1 at 0x7ffd19836c70 by thread T1 (mutexes: write M12):
#0 WaitSignalLock::signal() /home/jae/projects/condition_variable/main.cpp:39:19 (a.out+0xd3687)
#1 main::$_1::operator()() const /home/jae/projects/condition_variable/main.cpp:67:24 (a.out+0xd3f2f)
#2 void std::__invoke_impl<void, main::$_1>(std::__invoke_other, main::$_1&&) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/bits/invoke.h:60:14 (a.out+0xd3eb1)
#3 std::__invoke_result<main::$_1>::type std::__invoke<main::$_1>(main::$_1&&) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/bits/invoke.h:95:14 (a.out+0xd3e01)
#4 void std::thread::_Invoker<std::tuple<main::$_1> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/thread:264:13 (a.out+0xd3db9)
#5 std::thread::_Invoker<std::tuple<main::$_1> >::operator()() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/thread:271:11 (a.out+0xd3d69)
#6 std::thread::_State_impl<std::thread::_Invoker<std::tuple<main::$_1> > >::_M_run() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/thread:215:13 (a.out+0xd3c9d)
#7 execute_native_thread_routine /build/gcc/src/gcc/libstdc++-v3/src/c++11/thread.cc:80:18 (libstdc++.so.6+0xcfb73)
Previous read of size 1 at 0x7ffd19836c70 by main thread (mutexes: write M12):
#0 WaitSignalLock::conditionMet() const /home/jae/projects/condition_variable/main.cpp:46:12 (a.out+0xd36ea)
#1 WaitSignalLock::waitForConditionVariableSignal(std::unique_lock<std::mutex>&, std::chrono::duration<long, std::ratio<1l, 1000l> > const&)::$_0::operator()() const /home/jae/projects/condition_variable/main.cpp:52:18 (a.out+0xd3af1)
#2 bool std::condition_variable::wait_until<std::chrono::_V2::steady_clock, std::chrono::duration<long, std::ratio<1l, 1000000000l> >, WaitSignalLock::waitForConditionVariableSignal(std::unique_lock<std::mutex>&, std::chrono::duration<long, std::ratio<1l, 1000l> > const&)::$_0>(std::unique_lock<std::mutex>&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long, std::ratio<1l, 1000000000l> > > const&, WaitSignalLock::waitForConditionVariableSignal(std::unique_lock<std::mutex>&, std::chrono::duration<long, std::ratio<1l, 1000l> > const&)::$_0) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/condition_variable:157:10 (a.out+0xd3a37)
#3 bool std::condition_variable::wait_for<long, std::ratio<1l, 1000l>, WaitSignalLock::waitForConditionVariableSignal(std::unique_lock<std::mutex>&, std::chrono::duration<long, std::ratio<1l, 1000l> > const&)::$_0>(std::unique_lock<std::mutex>&, std::chrono::duration<long, std::ratio<1l, 1000l> > const&, WaitSignalLock::waitForConditionVariableSignal(std::unique_lock<std::mutex>&, std::chrono::duration<long, std::ratio<1l, 1000l> > const&)::$_0) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/condition_variable:185:9 (a.out+0xd378e)
#4 WaitSignalLock::waitForConditionVariableSignal(std::unique_lock<std::mutex>&, std::chrono::duration<long, std::ratio<1l, 1000l> > const&) /home/jae/projects/condition_variable/main.cpp:51:32 (a.out+0xd3608)
#5 WaitSignalLock::wait(std::chrono::duration<long, std::ratio<1l, 1000l> > const&) /home/jae/projects/condition_variable/main.cpp:29:25 (a.out+0xd3572)
#6 main /home/jae/projects/condition_variable/main.cpp:70:40 (a.out+0xd3860)
As if synchronized via sleep:
#0 nanosleep <null> (a.out+0x6aadc)
#1 void std::this_thread::sleep_for<long, std::ratio<1l, 1000l> >(std::chrono::duration<long, std::ratio<1l, 1000l> > const&) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/thread:405:9 (a.out+0xd556a)
#2 main::$_1::operator()() const /home/jae/projects/condition_variable/main.cpp:66:9 (a.out+0xd3f1f)
#3 void std::__invoke_impl<void, main::$_1>(std::__invoke_other, main::$_1&&) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/bits/invoke.h:60:14 (a.out+0xd3eb1)
#4 std::__invoke_result<main::$_1>::type std::__invoke<main::$_1>(main::$_1&&) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/bits/invoke.h:95:14 (a.out+0xd3e01)
#5 void std::thread::_Invoker<std::tuple<main::$_1> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/thread:264:13 (a.out+0xd3db9)
#6 std::thread::_Invoker<std::tuple<main::$_1> >::operator()() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/thread:271:11 (a.out+0xd3d69)
#7 std::thread::_State_impl<std::thread::_Invoker<std::tuple<main::$_1> > >::_M_run() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/thread:215:13 (a.out+0xd3c9d)
#8 execute_native_thread_routine /build/gcc/src/gcc/libstdc++-v3/src/c++11/thread.cc:80:18 (libstdc++.so.6+0xcfb73)
Location is stack of main thread.
Location is global '??' at 0x7ffd19818000 ([stack]+0x00000001ec70)
Mutex M12 (0x7ffd19836c48) created at:
#0 pthread_mutex_lock <null> (a.out+0x7e358)
#1 __gthread_mutex_lock(pthread_mutex_t*) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/x86_64-pc-linux-gnu/bits/gthr-default.h:749:12 (a.out+0xd39a6)
#2 std::mutex::lock() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/bits/std_mutex.h:100:17 (a.out+0xd45e9)
#3 std::unique_lock<std::mutex>::lock() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/bits/unique_lock.h:138:17 (a.out+0xd46df)
#4 std::unique_lock<std::mutex>::unique_lock(std::mutex&) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/bits/unique_lock.h:68:2 (a.out+0xd40b3)
#5 WaitSignalLock::wait(std::chrono::duration<long, std::ratio<1l, 1000l> > const&) /home/jae/projects/condition_variable/main.cpp:28:34 (a.out+0xd3563)
#6 main /home/jae/projects/condition_variable/main.cpp:70:40 (a.out+0xd3860)
Thread T1 (tid=16423, running) created by main thread at:
#0 pthread_create <null> (a.out+0x8dbce)
#1 __gthread_create /build/gcc/src/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/x86_64-pc-linux-gnu/bits/gthr-default.h:663:35 (libstdc++.so.6+0xcfe49)
#2 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) /build/gcc/src/gcc/libstdc++-v3/src/c++11/thread.cc:135:37 (libstdc++.so.6+0xcfe49)
#3 main /home/jae/projects/condition_variable/main.cpp:65:17 (a.out+0xd3816)
SUMMARY: ThreadSanitizer: data race /home/jae/projects/condition_variable/main.cpp:39:19 in WaitSignalLock::signal()
==================
ThreadSanitizer: reported 2 warnings
我不明白为什么会报告第一个和第二个警告。
- 第一个警告声称互斥锁被锁定了两次,但这怎么可能呢?互斥锁在持有它时没有被同一个线程锁定,据我所知,这就是 tsan 所声称的。
- 第二个警告表明变量“条件”不是以线程安全的方式读/写的。但是,它只有在互斥锁被锁定时才被读取和写入。还是 condition_variable::wait_for 返回后锁没有锁定?根据https://en.cppreference.com/w/cpp/thread/condition_variable/wait_for,锁应该被锁定。
这可能是 clang/tsan 中的错误吗?我找不到有关此问题的错误报告,并且很难相信情况确实如此。但我根本看不出上面的代码有什么问题。
我使用以下命令编译源代码:
clang++ -O1 -g -fsanitize=thread -fno-omit-frame-pointer -lpthread main.cpp
铿锵版本是
$ clang --version
clang version 10.0.0