简体   繁体   English

ThreadSanitizer:使用 std::jthread 双重锁定互斥锁

[英]ThreadSanitizer: double lock of a mutex with std::jthread

GCC thread sanitizer reports "double lock of a mutex" warning with code below: GCC thread sanitizer 报告“double lock of a mutex”警告,代码如下:

#include <mutex>
#include <condition_variable>
#include <chrono>
#include <stop_token>
#include <thread>

template<typename Rep, typename Period>
void sleep_for(const std::chrono::duration<Rep, Period>& d, const std::stop_token& token)
{
    std::mutex mutex;

    std::unique_lock<std::mutex> lock{ mutex };

    std::condition_variable_any().wait_for(lock, token, d, [&token]
    {
        return false;
    });
}

int main()
{
    std::jthread watch_dog_thread([](std::stop_token token)
    {
        sleep_for(std::chrono::seconds(std::chrono::seconds(3)), token);
    });

    std::this_thread::sleep_for(std::chrono::seconds(1));

    return 0;
}

GCC sanitizer output: GCC 消毒剂 output:

==================
    WARNING: ThreadSanitizer: double lock of a mutex (pid=6767)
    #0 pthread_mutex_lock ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:4250 (libtsan.so.0+0x53908)
    #1 __gthread_mutex_lock /usr/include/x86_64-linux-gnu/c++/11/bits/gthr-default.h:749 (MyAppTest+0x83c54)
    #2 std::mutex::lock() /usr/include/c++/11/bits/std_mutex.h:100 (MyAppTest+0x83fd2)
    #3 std::lock_guard<std::mutex>::lock_guard(std::mutex&) /usr/include/c++/11/bits/std_mutex.h:229 (MyAppTest+0x86974)
    #4 std::_V2::condition_variable_any::notify_all() /usr/include/c++/11/condition_variable:299 (MyAppTest+0x857f6)
    #5 operator() /usr/include/c++/11/condition_variable:404 (MyAppTest+0x2d9556)
    #6 _S_execute /usr/include/c++/11/stop_token:638 (MyAppTest+0x2d9d19)
    #7 std::stop_token::_Stop_cb::_M_run() /usr/include/c++/11/stop_token:148 (MyAppTest+0x849fd)
    #8 std::stop_token::_Stop_state_t::_M_request_stop() /usr/include/c++/11/stop_token:256 (MyAppTest+0x84d2a)
    #9 std::stop_source::request_stop() const /usr/include/c++/11/stop_token:536 (MyAppTest+0x8550c)
    #10 std::jthread::request_stop() /usr/include/c++/11/thread:201 (MyAppTest+0x856a6)
    #11 std::jthread::~jthread() /usr/include/c++/11/thread:129 (MyAppTest+0x8555b)
    #12 main /home/def/repos/MyApp/Tests/main.cpp:38 (MyAppTest+0x2d90c1)

Location is heap block of size 56 at 0x7b1000002000 allocated by thread T1:
    #0 operator new(unsigned long) ../../../../src/libsanitizer/tsan/tsan_new_delete.cpp:64 (libtsan.so.0+0x8f542)
    #1 __gnu_cxx::new_allocator<std::_Sp_counted_ptr_inplace<std::mutex, std::allocator<std::mutex>, (__gnu_cxx::_Lock_policy)2> >::allocate(unsigned long, void const*) /usr/include/c++/11/ext/new_allocator.h:121 (MyAppTest+0x8b350)
    #2 std::allocator<std::_Sp_counted_ptr_inplace<std::mutex, std::allocator<std::mutex>, (__gnu_cxx::_Lock_policy)2> >::allocate(unsigned long) /usr/include/c++/11/bits/allocator.h:173 (MyAppTest+0x8af4f)
    #3 std::allocator_traits<std::allocator<std::_Sp_counted_ptr_inplace<std::mutex, std::allocator<std::mutex>, (__gnu_cxx::_Lock_policy)2> > >::allocate(std::allocator<std::_Sp_counted_ptr_inplace<std::mutex, std::allocator<std::mutex>, (__gnu_cxx::_Lock_policy)2> >&, unsigned long) /usr/include/c++/11/bits/alloc_traits.h:460 (MyAppTest+0x8af4f)
    #4 std::__allocated_ptr<std::allocator<std::_Sp_counted_ptr_inplace<std::mutex, std::allocator<std::mutex>, (__gnu_cxx::_Lock_policy)2> > > std::__allocate_guarded<std::allocator<std::_Sp_counted_ptr_inplace<std::mutex, std::allocator<std::mutex>, (__gnu_cxx::_Lock_policy)2> > >(std::allocator<std::_Sp_counted_ptr_inplace<std::mutex, std::allocator<std::mutex>, (__gnu_cxx::_Lock_policy)2> >&) /usr/include/c++/11/bits/allocated_ptr.h:97 (MyAppTest+0x8a96e)
    #5 std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count<std::mutex, std::allocator<std::mutex>>(std::mutex*&, std::_Sp_alloc_shared_tag<std::allocator<std::mutex> >) /usr/include/c++/11/bits/shared_ptr_base.h:648 (MyAppTest+0x8a1d9)
    #6 std::__shared_ptr<std::mutex, (__gnu_cxx::_Lock_policy)2>::__shared_ptr<std::allocator<std::mutex>>(std::_Sp_alloc_shared_tag<std::allocator<std::mutex> >) /usr/include/c++/11/bits/shared_ptr_base.h:1337 (MyAppTest+0x8996e)
    #7 std::shared_ptr<std::mutex>::shared_ptr<std::allocator<std::mutex>>(std::_Sp_alloc_shared_tag<std::allocator<std::mutex> >) /usr/include/c++/11/bits/shared_ptr.h:409 (MyAppTest+0x88b8d)
    #8 std::shared_ptr<std::mutex> std::allocate_shared<std::mutex, std::allocator<std::mutex>>(std::allocator<std::mutex> const&) /usr/include/c++/11/bits/shared_ptr.h:861 (MyAppTest+0x87c56)
    #9 std::shared_ptr<std::mutex> std::make_shared<std::mutex>() /usr/include/c++/11/bits/shared_ptr.h:877 (MyAppTest+0x8688f)
    #10 std::_V2::condition_variable_any::condition_variable_any() /usr/include/c++/11/condition_variable:283 (MyAppTest+0x85763)
    #11 sleep_for<long int, std::ratio<1> > /home/def/repos/MyApp/Tests/main.cpp:21 (MyAppTest+0x2d91c0)
    #12 operator() /home/def/repos/MyApp/Tests/main.cpp:32 (MyAppTest+0x2d9016)
    #13 __invoke_impl<void, main()::<lambda(std::stop_token)>, std::stop_token> /usr/include/c++/11/bits/invoke.h:61 (MyAppTest+0x2da0db)
    #14 __invoke<main()::<lambda(std::stop_token)>, std::stop_token> /usr/include/c++/11/bits/invoke.h:96 (MyAppTest+0x2d9fd2)
    #15 _M_invoke<0, 1> /usr/include/c++/11/bits/std_thread.h:253 (MyAppTest+0x2d9ede)
    #16 operator() /usr/include/c++/11/bits/std_thread.h:260 (MyAppTest+0x2d9e6a)
    #17 _M_run /usr/include/c++/11/bits/std_thread.h:211 (MyAppTest+0x2d9e20)
    #18 <null> <null> (libstdc++.so.6+0xda6b3)

Mutex M51 (0x7b1000002010) created at:
    #0 pthread_mutex_lock ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:4250 (libtsan.so.0+0x53908)
    #1 __gthread_mutex_lock /usr/include/x86_64-linux-gnu/c++/11/bits/gthr-default.h:749 (MyAppTest+0x83c54)
    #2 std::mutex::lock() /usr/include/c++/11/bits/std_mutex.h:100 (MyAppTest+0x83fd2)
    #3 std::unique_lock<std::mutex>::lock() /usr/include/c++/11/bits/unique_lock.h:139 (MyAppTest+0x87e19)
    #4 std::unique_lock<std::mutex>::unique_lock(std::mutex&) /usr/include/c++/11/bits/unique_lock.h:69 (MyAppTest+0x86c78)
    #5 wait_until<std::unique_lock<std::mutex>, std::chrono::_V2::steady_clock, std::chrono::duration<long int, std::ratio<1, 1000000000> >, (anonymous namespace)::sleep_for<long int, std::ratio<1> >(const std::chrono::duration<long int>&, const std::stop_token&)::<lambda()> > /usr/include/c++/11/condition_variable:410 (MyAppTest+0x2d9642)
    #6 wait_for<std::unique_lock<std::mutex>, long int, std::ratio<1>, (anonymous namespace)::sleep_for<long int, std::ratio<1> >(const std::chrono::duration<long int>&, const std::stop_token&)::<lambda()> > /usr/include/c++/11/condition_variable:435 (MyAppTest+0x2d93ef)
    #7 sleep_for<long int, std::ratio<1> > /home/def/repos/MyApp/Tests/main.cpp:21 (MyAppTest+0x2d9204)
    #8 operator() /home/def/repos/MyApp/Tests/main.cpp:32 (MyAppTest+0x2d9016)
    #9 __invoke_impl<void, main()::<lambda(std::stop_token)>, std::stop_token> /usr/include/c++/11/bits/invoke.h:61 (MyAppTest+0x2da0db)
    #10 __invoke<main()::<lambda(std::stop_token)>, std::stop_token> /usr/include/c++/11/bits/invoke.h:96 (MyAppTest+0x2d9fd2)
    #11 _M_invoke<0, 1> /usr/include/c++/11/bits/std_thread.h:253 (MyAppTest+0x2d9ede)
    #12 operator() /usr/include/c++/11/bits/std_thread.h:260 (MyAppTest+0x2d9e6a)
    #13 _M_run /usr/include/c++/11/bits/std_thread.h:211 (MyAppTest+0x2d9e20)
    #14 <null> <null> (libstdc++.so.6+0xda6b3)

Thread T1 (tid=6769, running) created by main thread at:
    #0 pthread_create ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:969 (libtsan.so.0+0x605f8)
    #1 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) <null> (libstdc++.so.6+0xda989)
    #2 _S_create<main()::<lambda(std::stop_token)> > /usr/include/c++/11/thread:217 (MyAppTest+0x2d94c0)
    #3 jthread<main()::<lambda(std::stop_token)> > /usr/include/c++/11/thread:118 (MyAppTest+0x2d9301)
    #4 main /home/def/repos/MyApp/Tests/main.cpp:33 (MyAppTest+0x2d908a)

SUMMARY: ThreadSanitizer: double lock of a mutex /usr/include/x86_64-linux-gnu/c++/11/bits/gthr-default.h:749 in __gthread_mutex_lock
==================
==================
WARNING: ThreadSanitizer: lock-order-inversion (potential deadlock) (pid=6767)
Cycle in lock order graph: M48 (0x7fa6e2e9ece0) => M51 (0x7b1000002010) => M48

Mutex M51 acquired here while holding mutex M48 in thread T1:
    #0 pthread_mutex_lock ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:4250 (libtsan.so.0+0x53908)
    #1 __gthread_mutex_lock /usr/include/x86_64-linux-gnu/c++/11/bits/gthr-default.h:749 (MyAppTest+0x83c54)
    #2 std::mutex::lock() /usr/include/c++/11/bits/std_mutex.h:100 (MyAppTest+0x83fd2)
    #3 std::unique_lock<std::mutex>::lock() /usr/include/c++/11/bits/unique_lock.h:139 (MyAppTest+0x87e19)
    #4 std::unique_lock<std::mutex>::unique_lock(std::mutex&) /usr/include/c++/11/bits/unique_lock.h:69 (MyAppTest+0x86c78)
    #5 wait_until<std::unique_lock<std::mutex>, std::chrono::_V2::steady_clock, std::chrono::duration<long int, std::ratio<1, 1000000000> >, (anonymous namespace)::sleep_for<long int, std::ratio<1> >(const std::chrono::duration<long int>&, const std::stop_token&)::<lambda()> > /usr/include/c++/11/condition_variable:410 (MyAppTest+0x2d9642)
    #6 wait_for<std::unique_lock<std::mutex>, long int, std::ratio<1>, (anonymous namespace)::sleep_for<long int, std::ratio<1> >(const std::chrono::duration<long int>&, const std::stop_token&)::<lambda()> > /usr/include/c++/11/condition_variable:435 (MyAppTest+0x2d93ef)
    #7 sleep_for<long int, std::ratio<1> > /home/def/repos/MyApp/Tests/main.cpp:21 (MyAppTest+0x2d9204)
    #8 operator() /home/def/repos/MyApp/Tests/main.cpp:32 (MyAppTest+0x2d9016)
    #9 __invoke_impl<void, main()::<lambda(std::stop_token)>, std::stop_token> /usr/include/c++/11/bits/invoke.h:61 (MyAppTest+0x2da0db)
    #10 __invoke<main()::<lambda(std::stop_token)>, std::stop_token> /usr/include/c++/11/bits/invoke.h:96 (MyAppTest+0x2d9fd2)
    #11 _M_invoke<0, 1> /usr/include/c++/11/bits/std_thread.h:253 (MyAppTest+0x2d9ede)
    #12 operator() /usr/include/c++/11/bits/std_thread.h:260 (MyAppTest+0x2d9e6a)
    #13 _M_run /usr/include/c++/11/bits/std_thread.h:211 (MyAppTest+0x2d9e20)
    #14 <null> <null> (libstdc++.so.6+0xda6b3)

    Hint: use TSAN_OPTIONS=second_deadlock_stack=1 to get more informative warning message

Mutex M48 acquired here while holding mutex M51 in thread T1:
    #0 pthread_mutex_lock ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:4250 (libtsan.so.0+0x53908)
    #1 __gthread_mutex_lock /usr/include/x86_64-linux-gnu/c++/11/bits/gthr-default.h:749 (MyAppTest+0x83c54)
    #2 std::mutex::lock() /usr/include/c++/11/bits/std_mutex.h:100 (MyAppTest+0x83fd2)
    #3 std::unique_lock<std::mutex>::lock() /usr/include/c++/11/bits/unique_lock.h:139 (MyAppTest+0x87e19)
    #4 std::_V2::condition_variable_any::_Unlock<std::unique_lock<std::mutex> >::~_Unlock() /usr/include/c++/11/condition_variable:272 (MyAppTest+0x888a5)
    #5 wait_until<std::unique_lock<std::mutex>, std::chrono::_V2::steady_clock, std::chrono::duration<long int, std::ratio<1, 1000000000> >, (anonymous namespace)::sleep_for<long int, std::ratio<1> >(const std::chrono::duration<long int>&, const std::stop_token&)::<lambda()> > /usr/include/c++/11/condition_variable:419 (MyAppTest+0x2d9708)
    #6 wait_for<std::unique_lock<std::mutex>, long int, std::ratio<1>, (anonymous namespace)::sleep_for<long int, std::ratio<1> >(const std::chrono::duration<long int>&, const std::stop_token&)::<lambda()> > /usr/include/c++/11/condition_variable:435 (MyAppTest+0x2d93ef)
    #7 sleep_for<long int, std::ratio<1> > /home/def/repos/MyApp/Tests/main.cpp:21 (MyAppTest+0x2d9204)
    #8 operator() /home/def/repos/MyApp/Tests/main.cpp:32 (MyAppTest+0x2d9016)
    #9 __invoke_impl<void, main()::<lambda(std::stop_token)>, std::stop_token> /usr/include/c++/11/bits/invoke.h:61 (MyAppTest+0x2da0db)
    #10 __invoke<main()::<lambda(std::stop_token)>, std::stop_token> /usr/include/c++/11/bits/invoke.h:96 (MyAppTest+0x2d9fd2)
    #11 _M_invoke<0, 1> /usr/include/c++/11/bits/std_thread.h:253 (MyAppTest+0x2d9ede)
    #12 operator() /usr/include/c++/11/bits/std_thread.h:260 (MyAppTest+0x2d9e6a)
    #13 _M_run /usr/include/c++/11/bits/std_thread.h:211 (MyAppTest+0x2d9e20)
    #14 <null> <null> (libstdc++.so.6+0xda6b3)

Thread T1 (tid=6769, running) created by main thread at:
    #0 pthread_create ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:969 (libtsan.so.0+0x605f8)
    #1 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) <null> (libstdc++.so.6+0xda989)
    #2 _S_create<main()::<lambda(std::stop_token)> > /usr/include/c++/11/thread:217 (MyAppTest+0x2d94c0)
    #3 jthread<main()::<lambda(std::stop_token)> > /usr/include/c++/11/thread:118 (MyAppTest+0x2d9301)
    #4 main /home/def/repos/MyApp/Tests/main.cpp:33 (MyAppTest+0x2d908a)

SUMMARY: ThreadSanitizer: lock-order-inversion (potential deadlock) /usr/include/x86_64-linux-gnu/c++/11/bits/gthr-default.h:749 in __gthread_mutex_lock
==================
ThreadSanitizer: reported 2 warnings

but if I replace sleep_for function with this:但是如果我用这个替换sleep_for function :

template<typename Rep, typename Period>
void sleep_for(const std::chrono::duration<Rep, Period>& d, const std::stop_token& token)
{
    std::mutex mutex;

    std::unique_lock<std::mutex> lock{ mutex };

    std::condition_variable cv;

    std::stop_callback stop_wait
    {
        token,
        [&cv]()
        {
            cv.notify_one();
        }
    };

    cv.wait_for(lock, d, [&token]()
    {
        return token.stop_requested();
    });
}

GCC thread sanitizer stops reporting errors. GCC thread sanitizer 停止报告错误。

What can be the difference?有什么区别?

I am not sure if lambda in the first version should return false or token.stop_requested() , but the both alternatives have the same errors.我不确定第一个版本中的 lambda 是否应该返回falsetoken.stop_requested() ,但两种选择都有相同的错误。

Looks like it's a bug in gcc (the one I mentioned in the comments).看起来这是 gcc 中的一个错误(我在评论中提到的那个)。 When wait_for() is used and another thread tries to lock the same mutex then it triggers "double lock" warning.当使用wait_for()并且另一个线程试图锁定同一个互斥量时,它会触发“双重锁定”警告。 A simplified example:一个简化的例子:

#include <chrono>
#include <condition_variable>
#include <mutex>
#include <thread>
using namespace std::chrono_literals;

std::mutex mtx;
std::condition_variable cv;

void run1() {
    std::unique_lock lck{mtx};
    cv.wait_for(lck, 1s);
}
void run2() {
    std::unique_lock lck{mtx};
    std::this_thread::sleep_for(500ms);
    cv.notify_all();
}
int main() {
    std::jthread th1{ run1 };
    std::jthread th2{ run2 };
}

The reason why it doesn't trigger the warning in your second version of sleep_for() is because in the stop_callback you don't try to lock the mutex before calling cv.notify_one();它不会在您的第二个版本的sleep_for()中触发警告的原因是因为在stop_callback中您不会在调用cv.notify_one(); . . And std::condition_variable_any does exactly that, see condition_variable:404 and condition_variable:298 : std::condition_variable_any正是这样做的,请参阅condition_variable:404condition_variable:298

// line 404:
      std::stop_callback __cb(__stoken, [this] { notify_all(); });

// lines 295-300:
    void
    notify_all() noexcept
    {
      lock_guard<mutex> __lock(*_M_mutex);
      _M_cond.notify_all();
    }

I believe that corresponds to the stacktrace from your warning:我相信这对应于您警告中的堆栈跟踪:

#4 std::_V2::condition_variable_any::notify_all()
/usr/include/c++/11/condition_variable:299 (MyAppTest+0x857f6)
#5 operator()
/usr/include/c++/11/condition_variable:404 (MyAppTest+0x2d9556)

EDIT:编辑:

Based on Why does C++20 std::condition_variable not support std::stop_token?基于为什么 C++20 std::condition_variable 不支持 std::stop_token? your second version of sleep_for() probably has a race condition if that lock in the stop_callback is missing.如果stop_callback中的锁丢失,您的第二个版本的sleep_for()可能存在竞争条件。

So let's see, std::condition_variable::wait_for (2) says that it is equivalent to wait_until() and std::condition_variable::wait_until (2) says it is equivalent to:所以让我们看看, std::condition_variable::wait_for (2)说它等价于wait_until()std::condition_variable::wait_until (2)说它等价于:

while (!pred()) {
    if (wait_until(lock, timeout_time) == std::cv_status::timeout) {
        return pred();
    }
}

Some possible scenario:一些可能的情况:

  1. watchdog thread: inside wait_until() it gets a spurious wakeup, calls your predicate, which returns false.看门狗线程:在wait_until()内部它得到一个虚假的唤醒,调用你的谓词,它返回 false。 Before it gets a chance to call wait_until() again...在它有机会再次调用wait_until()之前......
  2. main thread: stops the watchdog thread, executes the stop_callback , which calls notify_all() ...主线程:停止看门狗线程,执行stop_callback ,调用notify_all() ...
  3. watchdog thread: misses the notification because it hasn't called wait_until() yet.看门狗线程:错过了通知,因为它还没有调用wait_until() It calls it now and waits until timeout (or another spurious wakeup).它现在调用它并等待直到超时(或另一个虚假唤醒)。

As a result, the thread doesn't stop when it should.结果,线程没有在应该停止的时候停止。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM