以下是将从多个线程调用的一段代码。 std::condition_variable::wait
的谓词对于每个帖子都会略有不同,但它不会改变问题。
std::unique_lock<std::mutex> lock{connection_mutex};
cv.wait(lock,
[conn = shared_from_this()]
{
return conn->connection_is_made();
}
);
//do some stuff
lock.unlock();
上看到了这个
当通知条件变量,超时到期或发生虚假唤醒时,线程被唤醒,并且原子重新获取互斥锁。然后线程应该检查条件并在唤醒是假的时候继续等待。
据此我了解lock
将被锁定,而不会检查谓词lambda,如果它返回true
,lock
将保持锁定状态,我可以继续执行某些操作在这个互斥锁的保护下的东西。当std::condition_variable
成员函数通知notify_one()
时,它非常有意义。
但是std::condition_variable
通知notify_all()
时会发生什么?我无法在文档中找到所有线程被唤醒而不是“等待一行”来锁定互斥锁,然后检查谓词返回的内容或者他们可能做其他事情。
std::condition_variable::wait
期望std::unique_lock
作为其第一个参数,并且在通知后唤醒std::condition_variable::wait
时,将重新获取std::unique_lock
。现在,如果多个线程正在等待相同的通知,那么最终在特定notify_all()
上调用std::condition_variable
时,只有1个线程可以锁定互斥锁,所有其他线程将重新进入休眠状态。所以,如果它与notify_all()
具有相同的效果,除非效率较低,我认为没有任何关于notify_one()
成员函数的意义。我的意思是如果必须重新获取互斥锁
一个要经过std::condition_variable::wait
的线程,那么所有等待的线程都无法同时执行它。
答案 0 :(得分:0)
我做了一些测试,看看在调用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> lck{M};
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> lck{M};
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> lck{M};
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> lck{M};
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> lck{M};
var = i;
CV.notify_all();
lck.unlock();
std::this_thread::sleep_for(std::chrono::seconds{1});
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
。