STL队列的线程安全性

19

我正在使用队列来在线程之间通信。我有一个读取者和多个写入者线程。我的问题是,当我为读取者使用push/front/pop从队列中获取或添加元素时,是否需要每次都锁定队列?我能否像下面这样做:

//reader threads
getLock();
get the number of elements from the queue
releaseLock();

int i = 0;
while( i < numOfElements){
    queue.front();
    queue.pop();
    i++
}

我的想法是减少锁定代码的粒度,因为写入线程只会写入队列的后面,并且只有一个读取线程。只要我获取到元素数量,那么我就可以从队列中获取元素。或者我需要在锁定内部同时包含front()pop()吗?

5个回答

12

正如其他人已经提到的,标准容器没有保证线程安全,因此您所要求的无法以可移植的方式实现。 您可以通过使用2个队列和一个队列指针来减少读取线程锁定写入者所需的时间。

每个写入者都会执行以下操作:

  • 获取锁
  • 将元素推入当前由队列指针指向的队列中
  • 释放锁

然后,读取器可以执行以下操作:

  • 获取锁
  • 将队列指针切换到指向第二个队列
  • 释放锁
  • 从第一个队列中处理元素

2
作者总是将数据追加到队列指针当前所指向的队列中(在获取锁之后)。在这种情况下,锁保护指针当前所引用的队列(以及指针本身);读取器可以在不持有锁的情况下处理另一个队列(“第一个队列”)。 - David Gelhar

9
任何没有明确说明其线程安全性保证的类型都应该由互斥锁进行控制。话虽如此,你的实现的stdlib可能允许某些变化 - 但你无法了解所有std::queue的实现。
由于std::queue包装另一个容器(它是一个容器适配器),因此您需要查看基础容器,默认为deque。
您可能会发现编写自己的容器适配器更容易、更好或更可移植,以满足您的需求。我不知道是否有任何东西在Boost中完全为队列做到这一点。
我还没有仔细研究过C++0x,不知道它是否有任何解决方案来处理这个问题,但这可能是另一种选择。

4
C++0x拥有原子操作,使程序员能够编写无锁(线程安全)算法,并提供标准的互斥锁,但它并没有任何开箱即用的内容。 - GManNickG

2
这绝对是与实现相关的。C++标准没有提到线程或线程安全性,因此这是否有效取决于您的实现如何处理队列元素。
在您的情况下,读取器实际上正在弹出队列,这被认为是写操作。我怀疑任何常见的实现在多个线程同时写入容器时都不会保证线程安全。至少VC++不会:
对于对同一对象的读取,当其他线程上没有写入者时,该对象对于读取是线程安全的。
对于对同一对象的写入,当其他线程上没有读取者时,该对象对于从一个线程进行写入是线程安全的。

1
有时候,通过避免在线程之间共享状态或资源,你可以解决很多并发问题。如果你有多个线程同时访问一个容器来推送它们的工作,那么尝试让它们分别在专用的容器上工作。然后,在特定的时间点,以非并发的方式将这些容器的元素收集到中央容器中。
如果你能够避免在线程之间共享状态或资源,那么你在并发运行线程时就没有任何问题。线程之间不需要担心彼此,因为它们完全隔离,并且对彼此没有任何影响。

1

你的直觉是正确的:尽管你不能指望 STD 队列是线程安全的,但队列应该从设计上就是线程安全的。

van Dooren 给出了一个很好的解释,以及 C++ 中线程安全、无锁队列的标准实现。


2
天真的无锁队列实现只针对特定情况,即单生产者、单消费者和代码生成器中有关内存屏障和原子读/写操作的一些假设,才部分“线程安全”。然而,实现真正的无锁线程安全队列是可能的,但并不简单。 - Leo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接