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

时间:2021-06-02 21:04:17

I'm wondering if this is the right approach to writing a thread-safe queue in C++?

我想知道用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;
};

You get the idea... I'm just wondering if this is the best approach. Thanks!

你懂的……我想知道这是不是最好的方法。谢谢!

EDIT: Because of the questions I'm getting, I wanted to clarify that the mutex is a boost::mutex

编辑:由于我收到的问题,我想澄清互斥是一个促进::互斥

6 个解决方案

#1


8  

Herb Sutter wrote an excellent article last year in Dr. Dobbs Journal, covering all of the major concerns for a thread-safe, lock-free, single-producer, single-consumer queue implementation. (Which made corrections over an implementation published the previous month.)

Herb Sutter去年在Dr. Dobbs Journal上写了一篇出色的文章,涵盖了线程安全、无锁、单生产者、单消费者队列实现的所有主要关注点。(在前一个月发布的一项执行情况下进行了更正。)

His followup article in the next issue tackled a more generic approach for a multi-user concurrent queue, along with a full discussion of potential pitfalls and performance issues.

下一期的后续文章讨论了多用户并发队列的一种更通用的方法,以及对潜在缺陷和性能问题的充分讨论。

There are a few more articles on similar concurrency topics.

还有一些关于类似并发主题的文章。

Enjoy.

享受。

#2


19  

I recommend using the Boost threading libraries to assist you with this.

我建议使用Boost线程库来帮助您实现这一点。

Your code is fine, except that when you write code in C++ like

您的代码很好,除了当您使用c++编写代码时

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

then if the code in the // do something section throws an exception then the lock will never be released. The Boost library solves this with its classes such as lock_guard in which you initialize an object which acquires a lock in its constructor, and whose destructor releases the lock. That way you know that your lock will always be released. Other languages accomplish this through try/finally statements, but C++ doesn't support this construct.

如果// do部分中的代码抛出异常,那么锁将永远不会释放。Boost库使用它的类来解决这个问题,例如,您初始化一个对象,该对象在其构造函数中获取一个锁,并且其析构函数释放锁。这样你就知道你的锁会被释放。其他语言通过try/finally语句实现这一点,但是c++不支持这种结构。

In particular, what happens when you try to read from a queue with no elements? Does that throw an exception? If so, then your code would run into problems.

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

When trying to get the first element, you probably want to check if something is there, then go to sleep if not and wait until something is. This is a job for a condition object, also provided by the Boost library, though available at a lower level if you prefer.

当尝试获取第一个元素时,您可能希望检查是否存在某个元素,如果没有,则进入休眠状态,并等待某个元素存在。这是一个条件对象的任务,也由Boost库提供,但是如果您愿意,可以在较低的级别上使用。

#3


6  

From a threading-point of view, that looks about right for a simple, thread-safe queue.

从线程的角度来看,它适用于简单的线程安全队列。

You do have one problem, though: std::queue's pop() does not return the element popped from the queue. What you need to do is:

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

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

You don't want to return a reference in this case since the referenced element is being popped from the queue and destroyed.

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

You also need to have some public Size() function to tell you how many elements are in the queue (either that, or you'll need to gracefully handle the case where Pop() is called and there are no elements in the queue).

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

Edit: Though, as Eli Courtwright points out, you do have to be careful with the queue operations throwing exceptions, and using Boost is a good idea.

编辑:但是,正如Eli Courtwright所指出的,您必须小心处理抛出异常的队列操作,使用Boost是一个好主意。

#4


1  

Depends what are your goals. Crafted in this manner, you'll have your "reader" client blocking your "writer" client. You might want to consider using a "condition" to avoid dead-locks etc.

这取决于你的目标是什么。以这种方式编写,您的“读者”客户端将阻塞“作者”客户端。您可能想要考虑使用“条件”来避免死锁等。

#5


1  

The approach that your are trying to implement is a locking approach. It will work, except that if you use a plain system-provided "mutex" object, it's performance might turn out disappointing (its lock-unlock overhead is pretty high). It is hard to say whether it will be good or not, since we don't know what your performance requirements and expectations are.

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

Since the operations you perform in "locked" segments of your code are rather quick, it might make sense to use a spin-lock instead of a true mutex, or a combination of the two. This will give you much better performance. Then again, maybe your "mutex" already implements all that (no way to know, since you provided no details about what is actually hiding behind that name).

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

And finally, if you are happen to be looking for best performance, you might want to read up on lock-free synchronization approach, which is a completely different story. Lock-free methods are typically much more difficult to implement though.

最后,如果您正在寻找最好的性能,您可能需要阅读无锁同步方法,这是一个完全不同的故事。不过,无锁方法通常更难实现。

#6


1  

As Jean-Lou Dupont pointed out, your current code is quite prone to deadlocks. When I've done this, I've used three locks: two counted semaphores and one mutex. The counted semaphores signal when:

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

  1. there's space available to insert an object
  2. 有空间可以插入对象
  3. There at least one object to retrieve from the queue
  4. 至少有一个对象要从队列中检索
The mutex is used only while actually putting an item in the queue or retrieving an item from the queue -- but no attempt is ever made at locking the mutex until we know that the insertion or retrieval will be able to succeed immediately.

#1


8  

Herb Sutter wrote an excellent article last year in Dr. Dobbs Journal, covering all of the major concerns for a thread-safe, lock-free, single-producer, single-consumer queue implementation. (Which made corrections over an implementation published the previous month.)

Herb Sutter去年在Dr. Dobbs Journal上写了一篇出色的文章,涵盖了线程安全、无锁、单生产者、单消费者队列实现的所有主要关注点。(在前一个月发布的一项执行情况下进行了更正。)

His followup article in the next issue tackled a more generic approach for a multi-user concurrent queue, along with a full discussion of potential pitfalls and performance issues.

下一期的后续文章讨论了多用户并发队列的一种更通用的方法,以及对潜在缺陷和性能问题的充分讨论。

There are a few more articles on similar concurrency topics.

还有一些关于类似并发主题的文章。

Enjoy.

享受。

#2


19  

I recommend using the Boost threading libraries to assist you with this.

我建议使用Boost线程库来帮助您实现这一点。

Your code is fine, except that when you write code in C++ like

您的代码很好,除了当您使用c++编写代码时

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

then if the code in the // do something section throws an exception then the lock will never be released. The Boost library solves this with its classes such as lock_guard in which you initialize an object which acquires a lock in its constructor, and whose destructor releases the lock. That way you know that your lock will always be released. Other languages accomplish this through try/finally statements, but C++ doesn't support this construct.

如果// do部分中的代码抛出异常,那么锁将永远不会释放。Boost库使用它的类来解决这个问题,例如,您初始化一个对象,该对象在其构造函数中获取一个锁,并且其析构函数释放锁。这样你就知道你的锁会被释放。其他语言通过try/finally语句实现这一点,但是c++不支持这种结构。

In particular, what happens when you try to read from a queue with no elements? Does that throw an exception? If so, then your code would run into problems.

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

When trying to get the first element, you probably want to check if something is there, then go to sleep if not and wait until something is. This is a job for a condition object, also provided by the Boost library, though available at a lower level if you prefer.

当尝试获取第一个元素时,您可能希望检查是否存在某个元素,如果没有,则进入休眠状态,并等待某个元素存在。这是一个条件对象的任务,也由Boost库提供,但是如果您愿意,可以在较低的级别上使用。

#3


6  

From a threading-point of view, that looks about right for a simple, thread-safe queue.

从线程的角度来看,它适用于简单的线程安全队列。

You do have one problem, though: std::queue's pop() does not return the element popped from the queue. What you need to do is:

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

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

You don't want to return a reference in this case since the referenced element is being popped from the queue and destroyed.

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

You also need to have some public Size() function to tell you how many elements are in the queue (either that, or you'll need to gracefully handle the case where Pop() is called and there are no elements in the queue).

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

Edit: Though, as Eli Courtwright points out, you do have to be careful with the queue operations throwing exceptions, and using Boost is a good idea.

编辑:但是,正如Eli Courtwright所指出的,您必须小心处理抛出异常的队列操作,使用Boost是一个好主意。

#4


1  

Depends what are your goals. Crafted in this manner, you'll have your "reader" client blocking your "writer" client. You might want to consider using a "condition" to avoid dead-locks etc.

这取决于你的目标是什么。以这种方式编写,您的“读者”客户端将阻塞“作者”客户端。您可能想要考虑使用“条件”来避免死锁等。

#5


1  

The approach that your are trying to implement is a locking approach. It will work, except that if you use a plain system-provided "mutex" object, it's performance might turn out disappointing (its lock-unlock overhead is pretty high). It is hard to say whether it will be good or not, since we don't know what your performance requirements and expectations are.

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

Since the operations you perform in "locked" segments of your code are rather quick, it might make sense to use a spin-lock instead of a true mutex, or a combination of the two. This will give you much better performance. Then again, maybe your "mutex" already implements all that (no way to know, since you provided no details about what is actually hiding behind that name).

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

And finally, if you are happen to be looking for best performance, you might want to read up on lock-free synchronization approach, which is a completely different story. Lock-free methods are typically much more difficult to implement though.

最后,如果您正在寻找最好的性能,您可能需要阅读无锁同步方法,这是一个完全不同的故事。不过,无锁方法通常更难实现。

#6


1  

As Jean-Lou Dupont pointed out, your current code is quite prone to deadlocks. When I've done this, I've used three locks: two counted semaphores and one mutex. The counted semaphores signal when:

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

  1. there's space available to insert an object
  2. 有空间可以插入对象
  3. There at least one object to retrieve from the queue
  4. 至少有一个对象要从队列中检索
The mutex is used only while actually putting an item in the queue or retrieving an item from the queue -- but no attempt is ever made at locking the mutex until we know that the insertion or retrieval will be able to succeed immediately.