【问题标题】:Deadlock after notify_one() (notifying thread locks mutex)notify_one() 后死锁(通知线程锁互斥锁)
【发布时间】:2016-02-25 19:38:58
【问题描述】:

我有以下问题。我有一些做一些工作的多个线程和一个在工作可用时唤醒它们的主线程。到目前为止,我已经设法使用条件变量和互斥锁编写了一些代码,并且大多数情况下这工作正常,但有时,通知线程会在调用 notify_one() 后立即锁定互斥锁,从而阻塞通知线程并死锁.

我已经编写了最少的代码来说明这种情况。

#include <iostream>
#include <thread>
#include <condition_variable>

std::mutex lock;
std::condition_variable cv;

void foo() {
    std::cout << "Thread: Entering doWork()" << std::endl;
    std::unique_lock<std::mutex> l(lock);
    std::cout << "Thread: Acquired lock, going to wait." << std::endl;
    cv.wait(l , []{return true;});
    std::cout << "Thread: Done waiting, exit." << std::endl;
}

int main(void) {
    std::unique_lock<std::mutex> l(lock);
    std::cout << "MAIN: Creating thread." << std::endl;
    std::thread t(foo);
    std::cout << "MAIN: Unlocking mutex." << std::endl;
    l.unlock();
    std::cout << "MAIN: Notifying thread." << std::endl;
    cv.notify_one();
    //std::this_thread::sleep_for(std::chrono::seconds(1));
    l.lock();
    std::cout << "MAIN: Acquired lock." << std::endl;
    std::cout << "MAIN: Joining thread." << std::endl;
    t.join();
    return 0;
}

理想情况下,输出应该是

MAIN: Creating thread.
MAIN: Unlocking mutex.
Thread: Entering doWork()
Thread: Acquired lock, going to wait.
MAIN: Notifying thread.
Thread: Done waiting, exit.
MAIN: Acquired lock.
MAIN: Joining thread.

但更多时候是这样

MAIN: Creating thread.
MAIN: Unlocking mutex.
MAIN: Notifying thread.
MAIN: Acquired lock.
MAIN: Joining thread.
Thread: Entering doWork()

除了在通知线程中添加睡眠(我不想这样做)之外,有没有更好的方法来消除死锁的机会?提前谢谢你。

【问题讨论】:

  • 您的cv.wait() 不处理虚假唤醒。根据en.cppreference.com/w/cpp/thread/condition_variable/wait:原子地释放锁,阻塞当前正在执行的线程,并将其添加到等待 *this 的线程列表中。执行 notify_all() 或 notify_one() 时,线程将被解除阻塞。它也可能被虚假地解除阻塞。解除阻塞后,无论什么原因,都会重新获取锁并等待退出。
  • 您已经错过了notify/wait 方案的全部逻辑!你忘了实现你正在等待的东西,改变线程需要通知的东西!你有一个wait 不等待 任何事情,还有一个notify 不通知 任何事情!
  • @AndrewHenle,线程确实释放了锁,这就是unique_lock 的用途。如果它测试了一个适当的条件,它会处理虚假唤醒,而不仅仅是[]{ return true;},因为谓词在它醒来时会被测试,如果谓词是假的,它会再次等待。
  • @JonathanWakely-Doh!固定的。谢谢!
  • 对于第一个问题,unique_lock 的析构函数解锁了关联的互斥锁。对于第二个,你能更具体一点吗?

标签: multithreading c++11 deadlock


【解决方案1】:

它是“条件变量”而不是“条件变量”,之所以叫它是因为你用它来等待某个条件。

你没有这样做,你只是等待一个总是返回 true 的 lambda,这就是你的问题的原因。这和你的主线程一直持有锁的事实(为什么?!)

有时main 线程甚至在foo 线程启动之前就快速运行解锁、notify_one 和锁定。这意味着foo 线程错过了通知,然后尝试获取锁,但不能因为主线程拥有它。

条件变量不像信号量,notify_one` 调用不会设置以后可以检测到的状态。如果在 notify_one 调用发生时条件变量没有等待,那么它会错过它,并且永远消失。如果您错过通知然后睡觉,您将永远不会醒来。

解决方案是不添加任意睡眠,这不能解决任何问题(永远!)

正确的解决方案是有一个经过测试的条件,并在您不更新任何共享数据时停止持有锁。在下面的示例中,被测试的条件是“布尔值ready 是真的吗?”并且foo 线程将等到该条件为真。主线程设置变量,使条件为真,然后通知其他线程它应该重新检查条件。

#include <iostream>
#include <thread>
#include <condition_variable>

std::mutex lock;
std::condition_variable cv;
bool ready = false;

void foo() {
    std::cout << "Thread: Entering doWork()" << std::endl;
    std::unique_lock<std::mutex> l(lock);
    std::cout << "Thread: Acquired lock, going to wait." << std::endl;
    cv.wait(l , []{return ready;});
    std::cout << "Thread: Done waiting, exit." << std::endl;
}

int main(void) {
    std::cout << "MAIN: Creating thread." << std::endl;
    std::thread t(foo);
    {
        std::cout << "MAIN: Locking mutex." << std::endl;
        std::unique_lock<std::mutex> l(lock);
        ready = true;
    }
    std::cout << "MAIN: Notifying thread." << std::endl;
    cv.notify_one();
    std::cout << "MAIN: Joining thread." << std::endl;
    t.join();
}

【讨论】:

  • 是的,我错过了我在实际代码中有谓词。还有一个问题,为什么互斥锁会被锁在 main 中?
  • 在您的示例中,将互斥锁锁定在 main 中是没有意义的,它完全没有意义并且会导致死锁。在我的示例中,互斥锁保护ready 变量,这是正在测试的“条件”。因为两个线程都可以访问该变量,所以您需要使用互斥锁来防止foo 在main 正在写入它时读取它。
猜你喜欢
  • 1970-01-01
  • 2015-10-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-25
  • 2013-01-31
  • 2011-03-03
  • 1970-01-01
相关资源
最近更新 更多