Queue.Queue与collections.deque
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Queue.Queue与collections.deque相关的知识,希望对你有一定的参考价值。
我需要一个队列,多个线程可以放入东西,多个线程可以读取。
Python至少有两个队列类,Queue.Queue和collections.deque,前者似乎在内部使用后者。两者都声称在文档中是线程安全的。
但是,队列文档还指出:
collections.deque是无界队列的替代实现,具有快速原子append()和popleft()操作,不需要锁定。
我想我不太沉思:这是否意味着deque毕竟不是完全线程安全的?
如果是,我可能不完全理解这两个类之间的区别。我可以看到Queue添加了阻止功能。另一方面,它失去了一些deque功能,如支持运营商。
直接访问内部deque对象是
X在队列中()。和
线程安全的?
另外,为什么当deque已经是线程安全的时候,Queue会使用互斥锁进行操作?
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
的内部双端队员正在玩火 - 你真的不想这样做。
如果您正在寻找的是一种在线程之间传输对象的线程安全方法,那么两者都可以工作(FIFO和LIFO都有)。对于FIFO:
注意:
- 关于
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 thread-safety(https://bugs.python.org/issue15329)引用的Python票证。标题“澄清哪种deque方法是线程安全的”
底线:https://bugs.python.org/issue15329#msg199368
deque的append(),appendleft(),pop(),popleft()和len(d)操作在CPython中是线程安全的。 append方法最后有一个DECREF(对于已经设置了maxlen的情况),但是在完成所有结构更新并且不变量已经恢复之后会发生这种情况,所以可以将这些操作视为原子操作。
无论如何,如果你不是100%确定并且你更喜欢可靠性而不是性能,那么就像锁一样;)
deque
上的所有单元素方法都是原子和线程安全的。所有其他方法也是线程安全的。像len(dq)
,dq[4]
这样的东西会产生瞬间正确的值。但想想,例如关于dq.extend(mylist)
:你不能保证mylist
中的所有元素都连续存在,而其他线程也在同一侧附加元素 - 但这通常不是线程间通信和被质询任务的要求。
因此,deque
比Queue
(在引擎盖下使用deque
)快20倍,除非你不需要“舒适”的同步API(阻塞/超时),严格的maxsize
服从或“覆盖这些方法(_put, _get,..)实现其他队列组织“子类行为,或者当你自己处理这些事情时,那么一个简单的deque
对于高速线程间通信来说是一个很好而有效的交易。
事实上,在._get()
中大量使用额外的互斥量和额外的方法Queue.py
等方法调用是由于向后兼容性限制,过去的过度设计和缺乏关注为线程间通信中的这个重要的速度瓶颈问题提供有效的解决方案。在旧的Python版本中使用了一个列表 - 但即使是list.append()/。pop(0)也是原子和线程安全的......
deque 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()操作,不需要锁定。 docs Queue
deque
是线程安全的。 “不需要锁定的操作”意味着您不必自己进行锁定,deque
负责处理。
看看Queue
源代码,内部deque被称为self.queue
,并使用互斥体作为访问器和突变,所以Queue().queue
不是线程安全的。
如果您正在寻找“in”运算符,那么deque或队列可能不是您问题的最合适的数据结构。
(似乎我没有评论的声誉......)你需要小心你从不同的线程使用哪种deque方法。
deque.get()似乎是线程安全的,但我发现这样做
for item in a_deque:
process(item)
如果另一个线程同时添加项目,则会失败。我得到一个RuntimeException抱怨“在迭代期间deque突变”。
检查collectionsmodule.c以查看受此影响的操作
以上是关于Queue.Queue与collections.deque的主要内容,如果未能解决你的问题,请参考以下文章
并发编程 - 线程 - 1.线程queue/2.线程池进程池/3.异步调用与回调机制