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

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

然而,Queue文档还声明:

Collections.deque是另一种选择 实现无界队列 使用快速原子append()和 Popleft()操作 需要锁定。

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

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

直接访问内部deque对象是

Queue().deque . x

线程安全?

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


当前回答

有关信息,有deque线程安全的Python票据引用(https://bugs.python.org/issue15329)。 标题“阐明哪些deque方法是线程安全的”

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

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

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

其他回答

在每个deque追加和popleft中添加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()

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

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

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

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

有关信息,有deque线程安全的Python票据引用(https://bugs.python.org/issue15329)。 标题“阐明哪些deque方法是线程安全的”

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

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

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

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

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

for item in a_deque:
   process(item)

如果另一个线程同时添加项,则可能失败。 我得到了一个RuntimeException,抱怨“deque在迭代期间突变”。

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

如果您所寻找的只是线程间传输对象的线程安全方法,那么这两种方法都可以工作(对于FIFO和LIFO都可以)。先进先出:

Queue.put()和Queue.get()是线程安全的 Deque.append()和deque.popleft()是线程安全的

注意:

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