我想知道这是否是在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;
};
你明白了......我只是想知道这是不是最好的方法。谢谢!
修改 由于我遇到的问题,我想澄清互斥是一个提升::互斥
答案 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指出的那样,您当前的代码很容易出现死锁。当我这样做时,我使用了三个锁:两个计数的信号量和一个互斥量。计算出的信号量在以下情况发出信号: