问题:Queue.Queue与collections.deque
我需要一个队列,多个线程可以将内容放入其中,并且多个线程可以读取。
Python至少有两个队列类,Queue.Queue和collections.deque,前者似乎在内部使用后者。两者都声称在文档中是线程安全的。
但是,队列文档还指出:
collections.deque是具有无限原子append()和popleft()操作的无界队列的替代实现,不需要锁定。
我猜我不太理解:这是否意味着双端队列毕竟不是完全线程安全的?
如果是这样,我可能无法完全理解两个类之间的区别。我可以看到Queue添加了阻止功能。另一方面,它失去了一些过时的功能,例如对操作员的支持。
直接访问内部双端队列对象是
Queue()中的x
线程安全的?
另外,当双端队列已经是线程安全的了,为什么Queue在操作上使用互斥锁?
回答 0
Queue.Queue
和 collections.deque
达到不同的目的。Queue.Queue旨在允许不同的线程使用排队的消息/数据进行通信,而collections.deque
仅仅是作为数据结构。这就是为什么Queue.Queue
有类似的方法put_nowait()
,get_nowait()
和join()
,而collections.deque
不会。Queue.Queue
不打算用作集合,这就是为什么它缺少in
运算符之类的原因。
归结为:如果您有多个线程,并且希望它们能够在不需要锁的情况下进行通信,那么您正在寻找Queue.Queue
;如果您只想将队列或双端队列作为数据结构,请使用collections.deque
。
最后,访问和处理内部的双端队列 Queue.Queue
正在玩火-您确实不想这样做。
回答 1
如果您要寻找的是一种在线程之间传输对象的线程安全方法,那么两者都将起作用(对于FIFO和LIFO都适用)。对于FIFO:
注意:
- 的其他操作
deque
我不确定可能不是线程安全的。 deque
并未阻挡pop()
或popleft()
让你无法立足于阻塞,直到一个新项目到达您的消费者线程流。
但是,似乎双端队列具有明显的效率优势。这是使用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
回答 2
有关信息,请参阅Python票证中的双端线程安全性(https://bugs.python.org/issue15329)。标题“阐明哪些双端队列方法是线程安全的”
底线在这里:https : //bugs.python.org/issue15329#msg199368
双端队列的append(),appendleft(),pop(),popleft()和len(d)操作在CPython中是线程安全的。append方法的末尾有一个DECREF(对于已设置maxlen的情况),但这会在所有结构更新完成并且不变量已恢复之后发生,因此可以将这些操作视为原子操作。
无论如何,如果您不确定100%的可靠性,而宁愿选择可靠性而不是性能,则只需放一个类似Lock的锁即可;)
回答 3
所有启用的单元素方法deque
都是原子和线程安全的。所有其他方法也是线程安全的。之类的东西len(dq)
,dq[4]
产生瞬间的正确的价值观。但是想想一下dq.extend(mylist)
:mylist
当其他线程也在同一侧附加元素时,您不能保证所有元素都连续提交,但这通常不是线程间通信和有问题的任务所必需的。
因此,a的deque
速度要快20倍左右Queue
(后者deque
在maxsize
幕后使用),除非您不需要“舒适的”同步API(阻止/超时),严格遵守或“覆盖这些方法(_put,_get,.. )来实现其他队列组织的子类化行为,或者当您自己处理此类事情时,光秃秃的deque
是高速线程间通信的好方法。
实际上,大量使用额外的互斥锁和额外的方法._get()
等Queue.py
是由于向后兼容性限制,过去的过度设计以及缺乏为线程间通信中这一重要的速度瓶颈问题提供有效解决方案的注意。在较旧的Python版本中使用了列表-但是list.append()/。pop(0)甚至是&都是原子和线程安全的…
回答 4
与默认行为的20倍改进相比,将notify_all()
每个结果相加会导致更差的结果:deque
append
popleft
deque
deque
deque + notify_all: 0.469802
Queue: 0.667279
@Jonathan稍微修改了他的代码,我使用cPython 3.6.2获得了基准,并在双端队列中添加了条件以模拟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()操作的无界队列的替代实现,不需要锁定。 docs队列
回答 5
deque
是线程安全的。“不需要锁定的操作”意味着您不必自己进行锁定,deque
多多关照。
以一看Queue
源,内部双端队列被称为self.queue
并使用存取和突变互斥,所以Queue().queue
是不线程安全的使用。
如果您正在寻找“ in”运算符,则双端队列或队列可能不是最适合您问题的数据结构。
回答 6
(似乎我没有信誉可言…)您需要注意从不同线程使用的双端队列的哪些方法。
deque.get()似乎是线程安全的,但是我发现这样做
for item in a_deque:
process(item)
如果另一个线程同时添加项目,则失败。我收到一个RuntimeException,它抱怨“迭代期间双端队列已变异”。
检查collectionsmodule.c以查看哪些操作受此影响