队列中。Queue vs. collections.deque

我需要一个队列,多个线程可以把东西放进去,多个线程可以从中读取。

Python至少有两个队列类,Queue.Queuecollections.deque,前者似乎在内部使用后者。两者在文档中都声称是线程安全的。

然而,Queue文档还声明:

collections.deque是另一个选项 实现无界队列 使用快速原子append()和 Popleft()操作 需要锁定。< /强> < / p >

我想我不太理解:这是否意味着deque不是完全线程安全的?

如果是的话,我可能无法完全理解这两个类的区别。我可以看到Queue增加了阻塞功能。另一方面,它失去了一些deque特性,比如对in-operator的支持。

直接访问内部deque对象是

Queue().deque . x

线程安全?

此外,当deque已经是线程安全的时候,为什么Queue要为它的操作使用互斥量?

91678 次浏览

deque是线程安全的。“不需要锁定的操作”意味着你不必自己进行锁定,deque会处理它。

看一下Queue源代码,内部deque被称为self.queue,并为访问器和突变使用互斥量,因此Queue().queue线程安全的。

如果您正在寻找一个“in”操作符,那么deque或queue可能不是最适合您的问题的数据结构。

Queue.Queuecollections.deque有不同的用途。队列中。Queue是为了允许不同的线程使用排队的消息/数据进行通信,而collections.deque只是作为一个数据结构。这就是为什么Queue.Queueput_nowait()get_nowait()join()这样的方法,而collections.deque没有。Queue.Queue不打算用作一个集合,这就是为什么它缺少类似in的操作符。

它可以归结为:如果你有多个线程,并且你希望它们能够在不需要锁的情况下进行通信,你将寻找Queue.Queue;如果你只是想要一个队列或双端队列作为数据结构,使用collections.deque

最后,访问和操作Queue.Queue的内部deque是在玩火——你真的不想这么做。

如果你要找的是线程间传输对象的线程安全方法,那么两者都可以(对于FIFO和LIFO都可以)。先进先出:

注意:

  • deque上的其他操作可能不是线程安全的,我不确定。
  • deque不会阻塞在pop()popleft()上,所以你不能让你的消费线程流阻塞直到新项到达。

然而,似乎Deque具有显著的效率优势。以下是使用CPython 2.7.3插入和删除100k项的一些以秒为单位的基准测试结果

deque 0.0747888759791
Queue 1.60079066852

下面是基准代码:

import time
import Queue
import collections


q = collections.deque()
t0 = time.clock()
for i in xrange(100000):
q.append(1)
for i in xrange(100000):
q.popleft()
print 'deque', time.clock() - t0


q = Queue.Queue(200000)
t0 = time.clock()
for i in xrange(100000):
q.put(1)
for i in xrange(100000):
q.get()
print 'Queue', time.clock() - t0

(似乎我没有声誉来评论…) 你需要注意从不同的线程中使用deque的哪些方法

deque.get()似乎是线程安全的,但我发现这样做

for item in a_deque:
process(item)
如果另一个线程同时添加项目,

将失败。 我得到了一个RuntimeException,抱怨“deque在迭代期间发生了突变”

检查collectionsmodule.c,看看哪些操作受此影响

供参考,有一个用于deque线程安全的Python票据(https://bugs.python.org/issue15329)。 标题"阐明哪些deque方法是线程安全的"

这里的底线:https://bugs.python.org/issue15329#msg199368

deque的append(), appendleft(), pop(), popleft()和len(d) 操作在CPython中是线程安全的。附加方法有一个 decf在结尾(对于maxlen已经设置的情况),但是这个 在进行了所有结构更新之后发生 不变量已经恢复,所以可以处理这些操作 原子。< / p >

不管怎样,如果你不是100%确定,你更喜欢可靠性而不是性能,就像锁;)

deque中的所有单元素方法都是原子的和线程安全的。所有其他方法也是线程安全的。像len(dq)dq[4]这样的东西会产生瞬时的正确值。但是想想例如dq.extend(mylist):当其他线程也在同一侧附加元素时,你不能保证mylist中的所有元素都被归档在一行中——但这通常不是线程间通信和被质疑任务的要求。

因此,dequeQueue(在底层使用deque)快20倍,除非你不需要“舒适”的同步API(阻塞/超时),严格遵守maxsize重写这些方法(_put, _get, ..)来实现其他队列组织子类化行为,或者当你自己处理这些事情时,那么一个纯deque对于高速线程间通信来说是一个很好的和有效的交易。

事实上,在Queue.py中大量使用额外的互斥量和额外的方法._get()等方法调用是由于向后兼容性限制、过去的过度设计和缺乏对线程间通信中这个重要的速度瓶颈问题提供有效解决方案的关注。在较旧的Python版本中使用了一个列表-但即使list.append()/.pop(0)也是&是原子的和线程安全的…

在每个deque appendpopleft中添加notify_all()会导致deque的结果比默认的deque行为实现了20倍的改进差得多:

deque + notify_all: 0.469802
Queue:              0.667279

@Jonathan稍微修改了一下他的代码,我使用cPython 3.6.2获得了基准测试,并在deque循环中添加了条件来模拟Queue的行为。

import time
from queue import Queue
import threading
import collections


mutex = threading.Lock()
condition = threading.Condition(mutex)
q = collections.deque()
t0 = time.clock()
for i in range(100000):
with condition:
q.append(1)
condition.notify_all()
for _ in range(100000):
with condition:
q.popleft()
condition.notify_all()
print('deque', time.clock() - t0)


q = Queue(200000)
t0 = time.clock()
for _ in range(100000):
q.put(1)
for _ in range(100000):
q.get()
print('Queue', time.clock() - t0)

而且似乎性能受到 这个函数condition.notify_all()

collections.deque是无界队列的另一种实现,具有不需要锁定的快速原子append()和popleft()操作。 文档队列 < / p >