这是线程安全的Queue类的正确方法吗?

时间:2009-10-18 02:31:04

标签: c++ multithreading concurrency thread-safety

我想知道这是否是在C ++中编写线程安全队列的正确方法?

template <class T>
class Queue
{
public:

Queue() {}

void Push(T& a)
{
    m_mutex.lock();
    m_q.push_back(a);
    m_mutex.unlock();
}

T& Pop()
{
    m_mutex.lock();
    T& temp = m_q.pop();
    m_mutex.unlock();
    return temp;
}

private:
    std::queue<t> m_q;
    boost::mutex m_mutex;
};

你明白了......我只是想知道这是不是最好的方法。谢谢!

修改 由于我遇到的问题,我想澄清互斥是一个提升::互斥

6 个答案:

答案 0 :(得分:19)

我建议使用the Boost threading libraries来帮助您解决此问题。

您的代码很好,除非您使用C ++编写代码,例如

some_mutex.lock();
// do something
some_mutex.unlock();

然后如果// do something部分中的代码抛出异常,则永远不会释放锁。 Boost库使用诸如lock_guard之类的类来解决这个问题,在其中初始化一个在其构造函数中获取锁的对象,并且其析构函数释放锁。这样你就知道你的锁将永远被释放。其他语言通过try / finally语句实现此目的,但C ++不支持此构造。

特别是,当您尝试从没有元素的队列中读取时会发生什么?这会引发异常吗?如果是这样,那么你的代码会遇到问题。

当试图获得第一个元素时,你可能想要检查是否存在某些东西,然后如果不存在就去睡觉并等到某个东西。这是一个condition对象的作业,也是由Boost库提供的,但如果您愿意,可以在较低级别使用。

答案 1 :(得分:8)

Herb Sutter去年在Dobbs Journal博士写了excellent article,涵盖了线程安全,无锁,单生产者,单一消费者队列实施的所有主要问题。 (这对上个月发布的实施做了更正。)

他的followup article在下一期中针对多用户并发队列解决了更通用的方法,并对潜在的陷阱和性能问题进行了全面讨论。

类似的并发主题有a few more articles

享受。

答案 2 :(得分:6)

从线程角度来看,这看起来适合于简单的,线程安全的队列。

但确实有一个问题:std :: queue的pop()不会返回从队列中弹出的元素。你需要做的是:

T Pop()
{
    m_mutex.lock();
    T temp = m_q.front();
    m_q.pop();
    m_mutex.unlock();
    return temp;
}

在这种情况下,您不希望返回引用,因为引用的元素正从队列中弹出并被销毁。

你还需要有一些公共的Size()函数来告诉你队列中有多少个元素(或者你需要优雅地处理调用Pop()并且没有元素的情况。队列)。

编辑尽管如此,正如Eli Courtwright指出的那样,你必须小心抛出异常的队列操作,使用Boost是一个好主意。

答案 3 :(得分:1)

取决于你的目标。以这种方式制作,你将让你的“读者”客户端阻止你的“作家”客户端。您可能需要考虑使用“条件”来避免死锁等。

答案 4 :(得分:1)

您尝试实施的方法是锁定方法。它会工作,除非你使用普通的系统提供的“互斥”对象,它的性能可能会令人失望(它的锁定解锁开销非常高)。很难说它是好还是不好,因为我们不知道你的性能要求和期望是什么。

由于您在代码的“锁定”段中执行的操作相当快,因此使用自旋锁而不是真正的互斥锁或两者的组合可能是有意义的。这将为您提供更好的性能。然后,也许你的“互斥体”已经实现了所有这些(无法知道,因为你没有提供关于该名称背后隐藏的内容的详细信息)。

最后,如果您正在寻找最佳性能,您可能想要了解无锁同步方法,这是一个完全不同的故事。无锁方法通常要难以实现。

答案 5 :(得分:1)

正如Jean-Lou Dupont指出的那样,您当前的代码很容易出现死锁。当我这样做时,我使用了三个锁:两个​​计数的信号量和一个互斥量。计算出的信号量在以下情况发出信号:

  1. 可用于插入对象的空间
  2. 至少有一个要从队列中检索的对象
  3. 只有在实际将项目放入队列或从队列中检索项目时才使用互斥锁 - 但在我们知道插入或检索能够立即成功之前,不会尝试锁定互斥锁。