线程安全队列实现:pop()的高效实现

Thread safe queue implementation : Efficient implementation of pop()

我正在尝试实现队列的线程安全版本,但在围绕 pop() 实现包装器时遇到问题。参考下面的代码。由于限制无法粘贴完整代码。

bool internal_pop_front_no_lock(T& item)
{
    bool isDataAvailable = false;

    if (!m_Queue.empty())
    {
        item = m_Queue.front();
        m_Queue.pop();
        isDataAvailable = true;
    }

   return isDataAvailable;
}

现在我觉得 item = m_Queue.front(); 行会复制数据。有什么办法可以避免复制?还是我误会了什么?

只有一种方法可以避免复制,即移动此对象然后 return 它。因为即使你可以在不复制的情况下访问 m_Queue.front(),只要你这样做 m_Queue.pop(); 这个对象就会被销毁,如果你需要在这个点之后访问这个对象,你需要复制或移动这个对象.所以像这样的事情是你唯一的机会:

std::optional<T> internal_pop_front_no_lock()
{
    std::optional<T> result;

    if (!m_Queue.empty())
    {
        result = std::move(m_Queue.front());
        m_Queue.pop();
    }

   return result;
}

由于 std::queue 存储实际值,您能做的最好的事情就是将值移出(如 std::move 中)。对于许多复杂的对象,即使复制很昂贵,移动也很便宜。

如果你有一个复杂的对象,移动起来很昂贵,你可以有一个 unique_ptr 的队列;这些移动起来很便宜(而且不可能复制)。

使用 std::unique_ptr<int> 或其他方法测试您的线程安全队列并确保其正常工作。然后你就会知道你的队列没有复制操作。然后由您队列中的用户(可能是您自己)来确保他们放入其中的物品足够便宜,可以移动。

A boost::optional<T>std::optional<T> 在这里非常有用,因为您有一个 T& 和一个 bool 来指示它是否已填充。但核心是:

result = std::move(m_Queue.front()); // move

而不是

result = m_Queue.front(); // copy

提取值时。

...

我自己对这种方法持怀疑态度。它的长名称是一个危险信号,它所做的只是包装一些 std::queue 方法。与直接调用 std::queue 方法相比,它看起来并没有降低复杂性。

根据我的经验,线程安全队列的棘手部分是决定通知如何工作、中止、批处理操作(pop_many、push_many)等。 queue 相比之下真的很简单。