3

这是一个使用 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
4

0 回答 0