C++ 为什么';使用谓词'等待;解决';迷失的觉醒';对于条件变量?

C++ 为什么';使用谓词'等待;解决';迷失的觉醒';对于条件变量?,c++,multithreading,synchronization,race-condition,condition-variable,C++,Multithreading,Synchronization,Race Condition,Condition Variable,我试图理解在条件变量的情况下,虚假唤醒和丢失唤醒之间的区别。下面是我试过的小代码。我知道在这种情况下“消费者”可能会在没有任何通知的情况下醒来,因此等待需要检查谓词 但wait with predicate如何解决“丢失唤醒”的问题呢?正如您在下面的代码中所看到的;'“等待”在5秒钟内没有被调用,我希望它会错过最初的几个通知;但与predate相比,它没有遗漏任何内容。这些通知是否保存以备将来等待 #include <iostream> #include <deque>

我试图理解在条件变量的情况下,虚假唤醒和丢失唤醒之间的区别。下面是我试过的小代码。我知道在这种情况下“消费者”可能会在没有任何通知的情况下醒来,因此等待需要检查谓词

但wait with predicate如何解决“丢失唤醒”的问题呢?正如您在下面的代码中所看到的;'“等待”在5秒钟内没有被调用,我希望它会错过最初的几个通知;但与predate相比,它没有遗漏任何内容。这些通知是否保存以备将来等待

#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;
}
#包括
#包括
#包括
#包括
标准:德克q;
std::互斥m;
std::条件变量cv;
无效转储_q()
{
用于(自动x:q){
std::cout原子“解锁并等待”操作可防止丢失唤醒。丢失唤醒的发生方式如下:

  • 我们获得了保护数据的锁
  • 我们检查是否需要等待,我们看到我们需要等待
  • 我们需要释放锁,否则其他线程无法访问数据
  • 我们等着醒来
  • 您可以在此处看到丢失唤醒的风险。在步骤3和步骤4之间,另一个线程可以获取锁并发送唤醒。我们已经释放了锁,因此另一个线程可以执行此操作,但我们还没有等待,因此无法获得信号

    只要第2步是在锁的保护下完成的,第3步和第4步是原子的,就不会有丢失唤醒的风险。在修改数据之前无法发送唤醒,而在另一个线程获得锁之前无法发送唤醒。由于第3步和第4步是原子的,任何看到锁已解锁的线程也必然会看到我们等待

    这种原子“解锁和等待”是条件变量的主要用途,也是它们必须始终与互斥体和谓词关联的原因


    在上面的代码中,消费者并没有等待前几个通知,因为它正在睡眠。在这种情况下,它是否缺少通知?这种情况是否与#3和#4之间的竞争条件相似

    不,不可能

    未等待的使用者持有或不持有锁。如果未等待的使用者持有锁,它不会错过任何东西。谓词持有锁时不能更改

    如果消费者没有持有锁,那么它错过了什么并不重要。当它检查是否应该在步骤2中锁定时,如果它错过了什么,它必然会在步骤2中看到它,并且它会看到它不需要等待,因此它不会等待它错过的唤醒

    因此,如果谓词使得线程不需要等待,那么线程将不会等待,因为它检查谓词。在步骤1之前,没有错过唤醒的机会


    唯一需要实际唤醒的时间是线程进入睡眠状态。原子解锁和睡眠确保线程只能在持有锁且需要等待的事情尚未发生时决定进入睡眠状态。

    在上面的代码中,消费者不会因为正在睡眠而等待前几个通知。这不是错误吗在这种情况下,sing notify?这种情况与#3和#4之间的比赛条件不相似吗?谢谢您的帮助。@spa我将更新我的答案。在这种情况下,第2步不可能发生,因此我们永远无法进入第4步。