排序多个线程如何在 std::condition_variable::notify_all 之后重新获取 std::unique_lock<std::mutex>

Posted

技术标签:

【中文标题】排序多个线程如何在 std::condition_variable::notify_all 之后重新获取 std::unique_lock<std::mutex>【英文标题】:Order how multiple threads reacquire std::unique_lock<std::mutex> after std::condition_variable::notify_all 【发布时间】:2017-04-22 17:45:28 【问题描述】:

这是一段将从多个线程调用的代码。 std::condition_variable::wait 的谓词对于每个线程都会略有不同,但不会改变问题。

std::unique_lock<std::mutex> lockconnection_mutex;
cv.wait(lock, 
     [conn = shared_from_this()]
     
         return conn->connection_is_made();
     
);

//do some stuff
lock.unlock();

我在cppreference读到这篇文章

当通知条件变量、超时过期或发生虚假唤醒时,线程被唤醒,互斥量被原子地重新获取。如果唤醒是虚假的,线程应该检查条件并继续等待。

据我所知,lock 将被锁定,然后会检查谓词 lambda,如果它返回 truelock 将保持锁定状态,我可以在此保护下继续做一些事情互斥体。当std::condition_variablenotify_one() 成员函数通知时,这是非常有意义的。

但是当std::condition_variablenotify_all() 通知时会发生什么?如果所有线程都被唤醒并且比“排队等待”来锁定互斥锁,然后才检查谓词返回什么,或者他们可能会做其他事情,我无法在文档中找到。


编辑:在看到下面的 cmets 后,我开始思考。 std::condition_variable::wait 期望 std::unique_lock 作为它的第一个参数,当 std::condition_variable::wait 在通知后被唤醒时 - std::unique_lock 将被重新获取。现在,如果多个线程正在等待同一个通知,那么当最终在特定的 std::condition_variable 上调用 notify_all() 时,只有 1 个线程能够锁定互斥锁,所有其他线程将重新进入睡眠状态。因此,如果 notify_all() 成员函数与 notify_one() 具有相同的效果,但效率较低,我根本看不到它有任何意义。我的意思是,如果必须重新获取互斥锁 一个线程通过std::condition_variable::wait,那么所有等待的线程不可能同时完成。

【问题讨论】:

如果您询问通知顺序,那么这可能会有所帮助:***.com/questions/15912322/… 感谢您的回答,但在这种情况下,我并不担心永远不会收到 notify_all() 信号。我只想知道多个线程在等待同一个condition_variable时是怎么做的,然后通过notify_all()接收通知,然后尝试获取同一个mutex。 这取决于操作系统调度程序。您无法知道哪个线程首先获得互斥锁。 他们在排队吗? 1 个线程得到它,做它的事情,当互斥锁被释放时,另一个线程立即重新获取互斥锁并检查它的谓词等等,直到所有等待的线程重新获得互斥锁? 根据cppreference,所有线程都被唤醒。但是,如果他们只是要为互斥锁“排队”,那么除了一个之外,其他所有人都会回去睡觉。那么在那种情况下,为什么要调用全部通知而不是通知一个呢?如果所有等待的线程可以同时取得进展,Notify all 很好。 【参考方案1】:

我做了一些测试,看看是否所有线程都在调用 notify_all() 后被唤醒,即使是那些没有计划作为第一个被唤醒的线程。

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

std::mutex M;
std::condition_variable CV;
int var = 0;


int main()

    std::thread t1
        []
            std::unique_lock<std::mutex> lckM;
            while(var != 1)
                CV.wait(lck);
                std::cout << "t1 woken up and (i != 1) = " 
                          << (var != 1) << std::endl;
            
        
    ;

    std::thread t2
        []
            std::unique_lock<std::mutex> lckM;
            while(var != 2)
                CV.wait(lck);
                std::cout << "t2 woken up and (i != 2) = " 
                          << (var != 2) << std::endl;
            
        
    ;

    std::thread t3
        []
            std::unique_lock<std::mutex> lckM;
            while(var != 3)
                CV.wait(lck);
                std::cout << "t3 woken up and (i != 3) = " 
                          << (var != 3) << std::endl;
            
        
    ;

    std::thread t4
        []
            std::unique_lock<std::mutex> lckM;
            while(var != 4)
                CV.wait(lck);
                std::cout << "t4 woken up and (i != 4) = " 
                          << (var != 4) << std::endl;
            
        
    ;

    for(int i = 0; i < 6; ++i)
        std::unique_lock<std::mutex> lckM;
        var = i;
        CV.notify_all();
        lck.unlock();
        std::this_thread::sleep_for(std::chrono::seconds1);
        std::cout << "\n\n";
    

    t1.join();
    t2.join();
    t3.join();
    t4.join();
  

这是结果

t3 woken up and (i != 3) = 1 //spurious wakeup


t3 woken up and (i != 3) = 1
t4 woken up and (i != 4) = 1
t2 woken up and (i != 2) = 1
t1 woken up and (i != 1) = 0


t3 woken up and (i != 3) = 1
t4 woken up and (i != 4) = 1
t2 woken up and (i != 2) = 0


t3 woken up and (i != 3) = 0
t4 woken up and (i != 4) = 1


t4 woken up and (i != 4) = 0

所以我很高兴地看到,无论第一次唤醒哪个线程,所有通知的线程都会以某种随机顺序唤醒,只需调用notify_all()。看起来当notify_all()(比如线程A)唤醒的线程之一在完成某些工作后解锁mutex 时,下一个线程通过对notify_all() 的相同调用唤醒,因为线程A 自动锁定与线程 A 刚刚解锁的 mutex 相同。这种情况一直持续到被notify_all() 唤醒的所有线程都锁定/解锁了用于保护共享数据的相同mutex

【讨论】:

以上是关于排序多个线程如何在 std::condition_variable::notify_all 之后重新获取 std::unique_lock<std::mutex>的主要内容,如果未能解决你的问题,请参考以下文章

C++ std::condition_variable 是什么 有什么用 条件变量 线程同步

std::condition_variable wait() 和 notify_one() 同步

std::condition_variable 在 std::condition_variable::notify_all() 从其他线程后未正确唤醒

详解 C++ 多线程的condition_variable

[C++11 多线程同步] --- 条件变量

MFC ResumeThread 和 std::condition_variable 等待后偶尔线程同步失败