0

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

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

4

2 回答 2

2

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

  1. 一个锁,用于互斥,以及
  2. 用于确定等待是否已满足的谓词

当一个线程被“正常”通知而另一个线程被虚假通知时的行为是其中一个线程(不管哪个线程跑得更快)获取锁并确认队列非空,然后弹出顶部元素并释放锁;在较快的线程释放锁之前,失去锁竞争的人不会获得锁,因此它会看到已经清空的队列并确定这是一个虚假的唤醒,重新进入睡眠状态。

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

于 2017-10-03T01:58:36.870 回答
0

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

除非您想指定哪个线程应该拥有权限,否则您必须更改实现。

于 2017-10-03T03:04:36.403 回答