为什么重新使用条件变量的互斥会导致死锁?

问题描述:

当我寻求在C++ 11中实现std::condition_variable的帮助时,我遇到了一个代码here。在上面的问题中,可以正确执行此类代码,但在函数void g()中添加注释行会导致偶尔产生死锁。我想知道为什么和std::condition_variable::wait()cpp reference真的让我困惑)的内在机制。提前致谢。为什么重新使用条件变量的互斥会导致死锁?

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

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

void f() { 
    { 
     std::unique_lock<std::mutex> lock(mtx); 
     cv.wait(lock); 
    } 
    std::cout << "f()\n"; 
} 

void g() { 
    // std::unique_lock<std::mutex> lock(mtx); adding this line will result in 
    //           deadlock. 
    std::this_thread::sleep_for(std::chrono::seconds(1)); 
    cv.notify_one(); 
} 

int main() { 
    for (int i = 1; i <= 100; i++) { 
     std::cout << i << std::endl; 
     std::thread t1{ f }; 
     std::thread t2{ g }; 
     t2.join(); 
     t1.join(); 
    } 
} 
+0

注释行无法导致循环。也许你的意思是僵局? –

+0

是的。这是一个僵局。对不起,我英文很差。 :) –

你应该与实际条件条件变量相关联,也占了虚假唤醒。在你的例子中,如果你先发送条件变量,然后通过wait()进入条件变量睡眠,那么你的代码会死锁。

所以,你的代码应该理想地看起来像以下(其中,如果你的信号你wait()睡觉前,变更后的条件将检测你不应该睡觉)

void f() { 
    { 
     std::unique_lock<std::mutex> lock(mtx); 
     while (some_boolean) { 
      cv.wait(lock); 
     } 
    } 
    std::cout << "f()\n"; 
} 

void g() { 
    std::unique_lock<std::mutex> lock(mtx); 
    change_some_boolean(); 
    cv.notify_one(); 
} 

注意,它不当您致电notify_one()g()时,该锁是否处于锁定状态。但是,您应该确保在change_some_boolean()之前持有锁。

+0

这不能解决问题。如果'g()'在'f()'之前运行,那么'f()'会挂起,因为没有人唤醒它。循环布尔值可避免**虚假唤醒**。它不会避免锁定逻辑问题。 –

+0

@PeteBecker'f()'会挂起? while循环会检测到'g()'在 – Curious

+0

之前运行@PeteBecker将条件与条件变量相关联是在这种情况下同步的关键之一。 'f()'将如何挂起? – Curious

答案是在例如在您提供的链接:Condition Variable on cppreference

// Manual unlocking is done before notifying, to avoid waking up 
// the waiting thread only to block again (see notify_one for details) 
lk.unlock(); 
cv.notify_one(); 

你的榜样然而,不前时,它应该使用notify_one()解锁你的局部变量。你g()功能应该是这样的:

void g() { 
    std::unique_lock<std::mutex> lock(mtx); 
    std::this_thread::sleep_for(std::chrono::seconds(1)); 
    lock.unlock(); 
    cv.notify_one(); 
} 
+0

我加入'''lock.unlock()'''后,我的代码仍然有一个死锁。 QAQ –

+0

然后你可能完全错过了信号。你可能应该在每一行添加一些'cout'语句和'endl'。我敢打赌,“信号”线程在等待之前运行。您可以尝试在启动它们两者之间进行睡眠以尝试并保证执行顺序 –

+0

@KevinAnderson - 不幸的是,插入I/O操作有时会改变计时,以至于最终隐藏问题。 –

创建,创建运行g()不保证f()将开始运行g()做之前线程之前运行f()线程。当g()首先启动它抓住锁,睡一秒钟,然后通知的条件变量。由于没有人正在等待条件,所以通知不起作用。当g()返回它释放锁。 然后f()获取锁并拨打wait()。没有人把它唤醒,并且f()只是继续等待。这不是一个僵局;任何线程仍然可以调用notify()并唤醒f()