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

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是线程安全的。“不需要锁定的操作”意味着你不必自己进行锁定,deque会处理它。

看一下Queue源,内部deque叫做self。queue(),并使用互斥量进行访问和突变,因此queue()。使用队列不是线程安全的。

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

在每个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()操作。 文档队列

队列中。Queue和collections.deque有不同的用途。队列中。Queue的目的是允许不同的线程使用排队的消息/数据进行通信,而collections.deque只是作为一个数据结构。这就是队列的原因。Queue有put_nowait()、get_nowait()和join()等方法,而collections.deque没有。队列中。Queue不打算用作集合,这就是为什么它缺乏类似于in操作符的特性。

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

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

如果您所寻找的只是线程间传输对象的线程安全方法,那么这两种方法都可以工作(对于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

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

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

for item in a_deque:
   process(item)

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

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