我试图了解在条件变量情况下虚假唤醒与丢失唤醒之间的区别。以下是我尝试过的一小段代码。我知道在这种情况下,“消费者”可能会在没有任何通知的情况下醒来,因此等待需要检查谓词。
但是,如何用谓词等待解决“丢失唤醒”的问题呢?如您在下面的代码中看到的; 5秒钟没有调用“wait”,我期望它会丢失前几条通知;但是只要有早,它就不会错过任何机会。是否保存了这些通知以备将来使用?
#include <iostream>
#include <deque>
#include <condition_variable>
#include <thread>
std::deque<int> q;
std::mutex m;
std::condition_variable cv;
void dump_q()
{
for (auto x: q) {
std::cout << x << std::endl;
}
}
void producer()
{
for(int i = 0; i < 10; i++) {
std::unique_lock<std::mutex> locker(m);
q.push_back(i);
std::cout << "produced: " << i << std::endl;
cv.notify_one();
std::this_thread::sleep_for(std::chrono::seconds(1));
locker.unlock();
}
}
void consumer()
{
while (true) {
int data = 0;
std::this_thread::sleep_for(std::chrono::seconds(5)); // <- should miss first 5 notications?
std::unique_lock<std::mutex> locker(m);
cv.wait(locker);
//cv.wait(locker, [](){return !q.empty();}); // <- this fixes both spurious and lost wakeups
data = q.front();
q.pop_front();
std::cout << "--> consumed: " << data << std::endl;
locker.unlock();
}
}
int main(int argc, char *argv[])
{
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
return 0;
}
最佳答案
它是原子的“解锁并等待”操作,可防止丢失唤醒。丢失的唤醒会以这种方式发生:
您可以在此处看到丢失唤醒的风险。在第3步和第4步之间,另一个线程可以获取锁并发送唤醒信息。我们已经释放了锁,所以另一个线程可以执行此操作,但是我们还没有等待,所以我们不会收到信号。
只要在锁的保护下完成了步骤2,并且步骤3和4是原子的,就不会丢失唤醒的风险。除非修改数据,否则无法发送唤醒;直到另一个线程获得了锁,才能进行唤醒。由于3和4是原子的,因此任何将锁视为未锁定的线程也必将看到我们在等待。
这种原子的“解锁并等待”是条件变量的主要目的,也是它们必须始终与互斥量和谓词关联的原因。
没有。不会发生的
没有等待的消费者持有该锁,或者没有持有该锁。如果没有等待的消费者持有该锁,那么它将不会错过任何东西。谓词持有锁时无法更改。
如果消费者没有握住锁,那么丢失什么都没关系。当它检查是否应该在步骤2中锁定时,如果错过了任何内容,则必然会在步骤2中看到它,并且将看到它不需要等待,因此它不会等待它错过的唤醒。
因此,如果谓词使得线程不需要等待,则该线程将不会等待,因为它会检查谓词。在步骤1之前没有机会错过唤醒。
唯一需要真正唤醒的时间是线程是否进入休眠状态。原子解锁和 sleep 可确保线程只能在持有锁且需要等待的事件尚未发生时才决定进入休眠状态。
关于c++ - 为什么 'wait with predicate'为条件变量解决 'lost wakeup'?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/56384817/