Queue.Queue与collections.deque

时间:2009-04-04 14:03:10

标签: python thread-safety queue

我需要一个队列,多个线程可以将内容放入其中,多个线程可以从中读取。

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

但是,队列文档也说明了:

  

collections.deque是另一种选择   无限队列的实现   快速原子追加()和   popleft()操作没有   需要锁定。

我想我不太沉思:这是否意味着deque毕竟不是完全线程安全的?

如果是,我可能不完全理解这两个类之间的区别。我可以看到Queue添加了阻止功能。另一方面,它失去了一些双端功能,如支持运营商。

直接访问内部deque对象是

  

x在队列中()。deque

线程安全?

另外,为什么当deque已经是线程安全的时候,Queue会使用互斥锁进行操作?

7 个答案:

答案 0 :(得分:231)

Queue.Queuecollections.deque有不同的用途。 Queue.Queue旨在允许不同的线程使用排队的消息/数据进行通信,而collections.deque仅用作数据结构。这就是Queue.Queue包含put_nowait()get_nowait()join()等方法的原因,而collections.deque却没有。 Queue.Queue并非旨在用作集合,这就是为什么它缺少in运算符之类的原因。

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

最后,访问和操纵Queue.Queue的内部双端队列正在玩火 - 你真的不想这样做。

答案 1 :(得分:38)

如果您正在寻找的是在线程之间传输对象的线程安全方式,那么两者都可以工作(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

答案 2 :(得分:6)

有关信息,有一个为deque thread-safety(https://bugs.python.org/issue15329)引用的Python票证。 标题“澄清哪种deque方法是线程安全的”

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

  

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

无论如何,如果你不是100%确定并且你更喜欢可靠性而不是性能,那么就像锁一样;)

答案 3 :(得分:4)

deque上的所有单元素方法都是原子和线程安全的。所有其他方法也是线程安全的。 len(dq)dq[4]之类的内容会产生一些正确的值。但想想,例如关于dq.extend(mylist):当其他线程也在同一侧附加元素时,你不能保证mylist中的所有元素都被连续归档 - 但这通常不是线程间通信的要求对于质疑的任务。

所以dequeQueue快20倍(它使用了deque),除非你不需要“舒适”的同步API(阻塞/超时) ),严格maxsize服从或“覆盖这些方法(_put,_get,..)来实现其他队列组织”子类化行为,或者当你处理这些事情时你自己,然后一个裸deque是一个高效的线程间通信的好方法。

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

答案 4 :(得分:3)

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

答案 5 :(得分:2)

deque是线程安全的。 “不需要锁定的操作”意味着您不必自己进行锁定,deque负责锁定。

查看Queue源代码,内部双端队列称为self.queue,并使用互斥锁进行访问器和突变,因此Queue().queue 不是线程 - 安全使用。

如果您正在寻找“in”运算符,那么deque或队列可能不是您问题的最合适的数据结构。

答案 6 :(得分:2)

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

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

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

会失败。 我得到了一个RuntimeException,抱怨" deque在迭代过程中发生变异"。

检查collectionsmodule.c以查看受此影响的操作