English 中文(简体)
Queue.Queue与collections.deque
原标题:
  • 时间:2009-04-04 14:03:09
  •  标签:

我需要一个队列,多个线程可以往里存放数据,也可以从中读取。

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

然而,队列文档也指出:

collections.deque is an alternative implementation of unbounded queues with fast atomic append() and popleft() operations that do not require locking.

我想我不太明白:这是否意味着deque并不完全线程安全?

如果是这样,我可能没有完全理解这两个类之间的区别。我可以看到Queue添加了阻塞功能。另一方面,它失去了一些deque的特性,比如支持in运算符。 如果是这样,我可能没有完全理解这两个类之间的区别。我可以看到Queue添加了阻塞功能。另一方面,它失去了一些deque的特性,比如支持in运算符。

访问内部deque对象直接,是

Queue().deque中的x。

线程安全? (Xiàn chéng ān quán?)

此外,为什么队列在操作时要使用互斥锁,因为双端队列已经是线程安全的了?

最佳回答

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的内部deque就像玩火-你真的不想那样做。

问题回答

如果你只是在寻找一个线程安全的方式来在线程之间传输对象,那么这两种方法都可以使用(无论是FIFO还是LIFO)。对于FIFO:

注意:

  • Other operations on deque might not be thread safe, I m not sure.
  • deque does not block on pop() or popleft() so you can t base your consumer thread flow on blocking till a new item arrives.

然而,deque似乎具有显著的效率优势。以下是使用CPython 2.7.3秒为单位插入和删除100,000项的一些基准结果。

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

For information there is a Python ticket referenced for deque thread-safety (https://bugs.python.org/issue15329). Title "clarify which deque methods are thread-safe"

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

The deque s append(), appendleft(), pop(), popleft(), and len(d) operations are thread-safe in CPython. The append methods have a DECREF at the end (for cases where maxlen has been set), but this happens after all of the structure updates have been made and the invariants have been restored, so it is okay to treat these operations as atomic.

无论如何,如果你不能百分之百确定并且你更加注重可靠性而不是性能,就选择像锁一样的选项 ;)

deque 上的所有单元素方法都是原子性的和线程安全的。所有其他方法也是线程安全的。例如,len(dq)dq[4] 等都会返回瞬间正确的值。但是请考虑例如 dq.extend(mylist):当其他线程也在同一侧附加元素时,您无法保证 mylist 中的所有元素都按顺序填充 - 但这通常不是线程间通信和所讨论的任务要求。

因此,dequeQueue 快大约20倍(Queue 在其背后使用deque),除非您不需要“舒适”的同步API(阻塞/超时),严格的 maxsize 顺从或“覆盖这些方法(_put、_get、...)来实现其他队列组织”子类行为,或者当您自己处理这些事情时,那么一个裸的deque 是高速线程间通信的一个很好且有效的选择。

其实,在Queue.py中对额外的互斥锁和额外的方法._get()等方法的大量使用是由于向后兼容性约束、过度设计和缺乏对提供有效的解决方案来处理线程间通信的重要速度瓶颈问题的关注不足。在旧版本的Python中使用了一个列表——但是即使是list.append()/.pop(0)也是原子的和线程安全的…

在每个 dequeappendpopleft 中添加 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)

And it seems the performance limited by this function condition.notify_all()

collections.deque is an alternative implementation of unbounded queues with fast atomic append() and popleft() operations that do not require locking. docs Queue

deque是线程安全的。"不需要锁定的操作"意味着您不必自行锁定,deque会自行处理。

查看Queue源码,内部deque被称为self.queue,使用互斥锁来进行访问和更改,因此使用Queue().queue不安全的。

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

(seems I don t have reputation to comment...) You need to be careful which methods of the deque you use from different threads.

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

for item in a_deque:
   process(item)

can fail if another thread is adding items at the same time. I got an RuntimeException that complained "deque mutated during iteration".

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





相关问题
热门标签