问题描述
在条件变量的情况下,我试图了解虚假唤醒与丢失唤醒之间的区别。以下是我尝试过的一小段代码。我知道在这种情况下,消费者可能会在没有任何通知的情况下唤醒,因此等待需要检查谓词。
I am trying to understand the difference between spurious vs lost wakeup in case of a condition variable. Following is small piece code I tried. I understand that 'consumer' in this case could wake up without any notification and therefore the wait needs to check for predicate.
但是,如何与谓词等待解决了问题丢失的唤醒?如下面的代码所示; 5秒钟内没有等待 wait,我希望它会错过前几条通知;但是只要有早,它就不会错过任何东西。是否保存了这些通知以备将来使用?
But how does wait with predicate solves the issue of 'lost wakeup'? As you can see in code below; 'wait' is not called for 5 seconds and I was expecting it to miss first few notifications; but with predate, it does not miss any. Are these notifications saved for future 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;
}
推荐答案
这是原子的解锁并等待操作,以防止丢失唤醒。丢失丢失的唤醒方式是这样的:
It is the atomic "unlock and wait" operation that prevents lost wakeups. A lost wakeup happens this way:
- 我们获得了保护数据的锁。
- 我们
- 我们需要释放锁,因为否则其他线程无法访问数据。
- 我们等待唤醒。
- We acquire the lock that protects the data.
- We check to see whether we need to wait and we see that we do.
- We need to release the lock because otherwise no other thread can access the data.
- We wait for a wakeup.
您可以在此处看到丢失唤醒的风险。在第3步和第4步之间,另一个线程可以获取锁并发送唤醒信息。我们已经释放了锁,所以另一个线程可以执行此操作,但是我们还没有等待,所以我们不会收到信号。
You can see the risk of a lost wakeup here. Between steps 3 and 4, another thread could acquire the lock and send a wakeup. We have released the lock, so another thread can do this, but we aren't waiting yet, so we wouldn't get the signal.
只要步骤2在锁的保护下完成操作,第3步和第4步是原子操作,没有丢失唤醒的风险。除非修改数据,否则无法发送唤醒;直到另一个线程获得了锁,才能进行唤醒。由于3和4是原子的,因此任何将锁视为解锁的线程也必将看到我们在等待。
So long as step 2 is done under the protection of the lock and steps 3 and 4 are atomic, there is no risk of a lost wakeup. A wakeup cannot be sent until the data is modified which cannot be done until another thread acquires the lock. Since 3 and 4 are atomic, any thread that sees the lock as unlocked will necessarily also see us waiting.
此原子解锁并等待是条件的主要目的变量及其必须始终与互斥体和谓词相关联的原因。
This atomic "unlock and wait" is the primary purpose of condition variables and the reason they must always be associated with a mutex and a predicate.
不是。不会发生。
不等待的消费者持有锁或不持有。如果不等待的消费者持有该锁,它将不会错过任何东西。
Either the consumer that is not waiting holds the lock or it doesn't. If the consumer that is not waiting holds the lock, it can't miss anything. The predicate cannot change when it holds the lock.
如果消费者未持有锁,则丢失的内容无关紧要。当它检查是否应该在步骤2中锁定时,如果错过了任何内容,则必然会在步骤2中看到它,并且将看到它不需要等待,因此它不会等待错过的唤醒。
If the consumer is not holding the lock, then it doesn't matter what it misses. When it checks to see whether it should lock in step 2, if it missed anything, it will necessarily see it in step 2 and it will see that it does not need to wait, so it will not wait for the wakeup that it missed.
因此,如果谓词使得线程不需要等待,则该线程将不会等待,因为它会检查谓词。在步骤1之前没有机会错过唤醒。
So if the predicate is such that the thread does not need to wait, the thread will not wait because it checks the predicate. There is no opportunity for a missed wakeup prior to step 1.
唯一需要真正唤醒的时间是线程进入睡眠状态。原子解锁和睡眠确保了线程只能在持有锁的同时还需要等待的事物尚未发生时才决定进入睡眠状态。
The only time an actual wakeup is needed is if a thread goes to sleep. The atomic unlock and sleep ensures that a thread can only decide to go to sleep while it holds the lock and while the thing it needs to wait for has not yet happened.
这篇关于为什么“用谓词等待”解决了条件变量的“丢失唤醒”?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!