【问题标题】:dead-lock with condition_variable带有条件变量的死锁
【发布时间】:2021-02-25 06:56:48
【问题描述】:

我在尝试从线程通知 condition_variable 时遇到死锁。

这是我的 MCVE:

#include <iostream>
#include <boost/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/condition_variable.hpp>

static boost::mutex m_mutex;
static boost::condition_variable m_cond;

void threadFunc()
{
    std::cout << "LOCKING MUTEX" << std::endl;
    boost::mutex::scoped_lock lock( m_mutex );
    std::cout << "LOCKED, NOTIFYING CONDITION" << std::endl;
    m_cond.notify_all();
    std::cout << "NOTIFIED" << std::endl;
}

int main( int argc, char* argv[] )
{
    while( true )
    {
        std::cout << "TESTING!!!" << std::endl;

        boost::mutex::scoped_lock lock( m_mutex );

        boost::thread thrd( &threadFunc );

        //m_cond.wait( lock );
        while ( !m_cond.timed_wait(lock,boost::posix_time::milliseconds(1)) )
        {
            std::cout << "WAITING..." << std::endl;
        }

        static int pos = 0;
        std::cout << "DONE!!! " << pos++ << std::endl;

        thrd.join();
    }

    return 0;
}

如果使用m_cond.wait( lock );,我看到每次尝试都会写入DONE!!!,这里没问题。

如果我使用while ( !m_cond.timed_wait(lock,boost::posix_time::milliseconds(1)) ) 循环,我会看到DONE!!! 被写入了几次尝试,并且在某些时候,我得到了一个死锁,等待终于永远不会结束:

TESTING!!!
LOCKING MUTEX
LOCKED, NOTIFYING CONDITION
NOTIFIED
WAITING...
WAITING...
WAITING...
WAITING...
WAITING...
WAITING...
...

我已经阅读了有关 stackoverflow 的其他帖子(例如 Condition variable deadlock):他们提到如果在条件的等待函数运行之前调用 notify_all 可能会发生这种情况,因此必须使用互斥锁来防止这种情况发生。但我觉得这就是我正在做的事情:

  • 我在创建线程之前锁定了互斥锁
  • 然后线程无法在到达m_cond.timed_wait 之前通知(然后互斥锁被解锁)
  • 在循环内,如果超时,timed_wait 会重新锁定互斥锁,因此无法完成通知,我们会打印“WITTING...”并在再次准备好接收通知时释放互斥锁

那么为什么会发生死锁呢?是否可以在timed_wait 检测到超时和重新锁定互斥锁之间通知条件?

【问题讨论】:

  • 我已经用 c++ 标准库试过了,它没有死锁:compiler-explorer.com/z/Gh7da7
  • @JVApen:点击了您的链接。在尝试 #54! 后进入无限循环,重试两次,没关系,第一次尝试,在尝试 #284 后它开始等待永远......所以这显然不是一个提升问题。
  • 你似乎没有防备spurious wakeups
  • ...另外,在持有锁时通知可能是一种悲观。
  • @TedLyngmo Rainer 解释了为什么在此处通知锁时需要保持互斥锁:modernescpp.com/index.php/… - 尽管在这种情况下它只会导致额外的毫秒等待而不是永远等待。跨度>

标签: c++ multithreading boost-thread condition-variable


【解决方案1】:

问题是,如果timed_wait 在调用notify_all 之前完成,那么它必须等待线程释放互斥锁(即在调用notify_all 之后),然后才能继续调用timed_wait再次,线程已经完成,所以timed_wait 永远不会成功。有两种情况会发生这种情况,如果您的线程启动时间超过一毫秒(应该不太可能,但您的操作系统的调度变化无常意味着它可能会发生,尤其是在 CPU 繁忙的情况下),另一种是虚假唤醒。

可以通过在调用notify_all 时设置一个标志来防止这两种情况,等待线程可以检查该标志以确保已调用通知:

#include <iostream>
#include <boost/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/condition_variable.hpp>

static boost::mutex m_mutex;
static boost::condition_variable m_cond;

void threadFunc(bool& notified)
{
    std::cout << "LOCKING MUTEX" << std::endl;
    boost::mutex::scoped_lock lock(m_mutex);
    std::cout << "LOCKED, NOTIFYING CONDITION" << std::endl;
    notified = true;
    m_cond.notify_all();
    std::cout << "NOTIFIED" << std::endl;
}

int main(int argc, char* argv[])
{
    while (true)
    {
        std::cout << "TESTING!!!" << std::endl;

        boost::mutex::scoped_lock lock(m_mutex);

        bool notified = false;

        boost::thread thrd(&threadFunc, boost::ref(notified));

        //m_cond.wait( lock );
        std::cout << "WAITING..." << std::endl;
        while (!m_cond.timed_wait(lock, boost::posix_time::milliseconds(1), [&] { return notified; }))
        {
            std::cout << "WAITING..." << std::endl;
        }

        static int pos = 0;
        std::cout << "DONE!!! " << pos++ << std::endl;

        thrd.join();
    }

    return 0;
}

【讨论】:

  • 谢谢,您的代码解决了这个问题。但我想明白。你的意思是 notify_all 被调用,而 main 在其 while 循环中打印“WAITING”,对吧?但是此时,互斥锁应该被锁定,因为timed_wait 应该在返回之前重新锁定它。你的意思是notify_alltimed_wait 检测到超时(此处互斥锁未锁定)和有机会重新锁定互斥锁之间调用?
  • 好吧,我认为timed_wait 足够聪明,可以防止这种情况发生。
  • 您的notified 变量不应该是atomic_bool吗?
  • @jpo38 觉得没必要,它只被一个线程修改并且已经被互斥锁保护了
【解决方案2】:

条件变量的等待必须在条件发出信号之前开始。使用您的代码,虚假唤醒可能允许线程在等待开始之前完成。

解决方案是这样的 - 不要只等待条件变量。测试一个共享标志,并使用条件变量在发出信号后立即唤醒。请在此处查看 Rainer 的指南: https://www.modernescpp.com/index.php/c-core-guidelines-be-aware-of-the-traps-of-condition-variables

另请参阅有关将 boost 条件变量与谓词一起使用的主题: boost::condition_variable - using wait_for with predicate

How do I use a boost condition variable to wait for a thread to complete processing?

【讨论】:

  • “使用您的代码,线程可能会在等待开始之前完成”。不,这是不可能的,主线程锁定了互斥锁,并通过调用timed_wait 将其释放。工作线程需要获得锁才能退出。
  • 这如何解释您在日志中看到“通知”之后的“等待”?
  • "WAITING..." 在timed_wait被调用之后打印,所以它也可能是"WAITED"...
  • 所以这可能是 Ted 建议的虚假唤醒。
猜你喜欢
  • 2013-04-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多