线程安全队列和虚假唤醒

Thread safe queues and spurious wakes

我目前正在阅读一本关于 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 中的一些(可能的)等待线程可以恢复工作。在这种情况下,让我感到困惑的是虚假唤醒。如果在通知一个线程的同时另一个线程刚好同时醒来怎么办?当然他也看到了一个非空队列。在这种情况下,两个不同的线程将尝试弹出一个值,其中可能只存在一个值 - 经典竞争条件。

我是不是漏掉了什么?有一个更好的方法吗?

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

  1. 一个锁,用于互斥,并且
  2. 判断是否满足等待的谓词

当一个线程被通知时的行为 "normally",而另一个线程被虚假通知时的行为是其中一个(无关紧要,以速度更快者为准)获取锁并确认队列非空, 然后弹出顶部元素并释放锁;在更快的线程释放锁之前,失去锁竞争的线程不会获得锁,因此它看到已经清空的队列并决定这是一个虚假的唤醒,然后回到睡眠状态。

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

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

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