本文介绍了为什么“等待谓词"解决了条件变量的“丢失唤醒"?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图了解在条件变量的情况下虚假唤醒与丢失唤醒之间的区别.以下是我尝试过的一小段代码.我知道在这种情况下,消费者"可能会在没有任何通知的情况下醒来,因此等待需要检查谓词.

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.

但是等待谓词如何解决丢失唤醒"的问题?正如您在下面的代码中看到的那样;'wait' 没有被调用 5 秒,我预计它会错过前几个通知;但早于,它不会错过任何.这些通知是否已保存以供将来等待?

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:

  1. 我们获取保护数据的锁.
  2. 我们检查是否需要等待,然后发现确实需要.
  3. 我们需要释放锁,否则没有其他线程可以访问数据.
  4. 我们等待唤醒.

您可以在此处查看丢失唤醒的风险.在步骤 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.

在上面的代码中,消费者没有等待前几个通知,因为它正在休眠.在这种情况下是否没有丢失通知?这种情况与#3 和#4 之间的竞态条件不相似吗?

不.不可能发生.

没有等待的消费者要么持有锁,要么没有.如果没有等待的消费者持有锁,它就不会错过任何东西.谓词在持有锁时不能更改.

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.

这篇关于为什么“等待谓词"解决了条件变量的“丢失唤醒"?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

05-28 19:03
查看更多