排序多个线程如何在 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,如果它返回 true
,lock
将保持锁定状态,我可以在此保护下继续做一些事情互斥体。当std::condition_variable
被notify_one()
成员函数通知时,这是非常有意义的。
但是当std::condition_variable
被notify_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() 从其他线程后未正确唤醒