线程安全队列和虚假唤醒

时间:2017-10-03 01:39:51

标签: c++ multithreading mutex condition-variable spurious-wakeup

我目前正在阅读一本关于C ++中多线程的书。在一章中,我找到了一些线程安全队列的源代码。它粗略地构建如下:

template<typename T>
class QueueThreadSafe
{
private:
    std::mutex m_mutex;
    std::queue<T> m_dataQueue;
    std::condition_variable m_dataCondition;

public:
    void push(T someValue)
    {
        std::lock_guard<std::mutex> guard(m_mutex);
        m_dataQueue.push(someValue);
        m_dataCondition.notify_one();
    }

    void pop(T &retVal)
    {
        std::unique_lock<std::mutex> lock(m_mutex);
        m_dataCondition.wait(lock, [this]{return !m_dataQueue.empty();});
        retVal = m_dataQueue.front();
        m_dataQueue.pop();
    }
};

当一个值被推送到队列时,会通知数据条件,并且pop中的一些(可能的)等待线程可以恢复工作。令我困惑的是这种情况下的虚假唤醒。如果同时通知一个线程,另一个线程同时唤醒该怎么办?当然,他也看到一个非空队列。在这种情况下,两个不同的线程会尝试弹出一个值,其中可能只存在一个值 - 一个经典的竞争条件。

我在这里错过了什么吗?有一个更好的方法吗?

2 个答案:

答案 0 :(得分:2)

虚假的醒来只是意味着当你被唤醒时你需要检查唤醒的条件是否仍然有效。由于传递了wait函数:

  1. 锁定,互斥,
  2. 确定是否已满足等待的谓词
  3. 当一个线程被“正常”通知而另一个被虚假通知时的行为是其中一个(无论哪个,无论哪个比赛更快)获得锁并确认队列是非空的,然后弹出顶部元素并释放锁;失去锁定竞争的那个没有获得锁定,直到更快的线程释放锁定,因此它看到已经清空的队列并判定它是一个虚假的唤醒,然后回到睡眠状态。

    重要的是,虚假的唤醒线程是否赢得了锁定(以及排队项目)的竞争并不重要;其中一个线程表现得好像正常被唤醒(它发现条件为真并且按预期工作),一个假如虚假地被唤醒(它发现条件为假并返回等待,如预期的那样),并且代码作为一个整体表现正确

答案 1 :(得分:0)

我认为在这种情况下,通知的线程和唤醒的线程有相同的机会从队列中弹出,它只取决于CPU如何做出日程安排决策(哪一个更快)。

除非你想指定哪个线程应该有权利,否则你必须改变实现。