Queue.Queue与collections.deque

mir*_*e2k 160 python queue thread-safety

我需要一个队列,多个线程可以放入东西,多个线程可以读取.

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

但是,队列文档还指出:

collections.deque是无界队列的替代实现,具有快速原子append()和popleft()操作,不需要锁定.

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

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

直接访问内部deque对象是

x在队列().deque

线程安全的?

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

Kei*_*han 255

Queue.Queuecollections.deque服务于不同的目的.Queue.Queue旨在允许不同的线程使用排队的消息/数据进行通信,而collections.deque只是用作数据结构.这就是为什么Queue.Queue有类似的方法put_nowait(),get_nowait()join(),而collections.deque不会.Queue.Queue不打算用作集合,这就是为什么它缺乏in运营商的喜欢.

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

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

  • @KeithGaughan `由于 GIL` 的存在,deque 偶然是线程安全的;`deque` 确实依赖 GIL 来确保线程安全 - 但这并非“偶然”。官方python文档明确指出`deque``pop*`/`append*`方法是线程安全的。因此,任何有效的 Python 实现都必须提供相同的保证(无 GIL 实现必须弄清楚如何在没有 GIL 的情况下做到这一点)。您可以放心地依赖这些保证。 (7认同)
  • 不,这根本不是一个好主意.如果你看一下`Queue.Queue`的来源,它会在引擎盖下使用`deque`.`collections.deque`是一个集合,而`Queue.Queue`是一种通信机制.`Queue.Queue`的开销是使它线程安全.使用`deque`进行线程之间的通信只会导致痛苦的比赛.每当`deque`恰好是线程安全的时候,这就是解释器实现方式的一个幸福意外,而不是*依赖的东西.这就是为什么`Queue.Queue`首先存在的原因. (5认同)
  • 请记住,如果跨线程通信,则使用双端队列(deque)会惹火。由于存在GIL,双端队列是线程安全的*是偶然的*。不含GIL的实现将具有完全不同的性能特征,因此,折衷其他实现是不明智的。此外,您是否将Queue vs deque定时在各个线程中使用,而不是在单个线程中使用天真的基准测试?如果您的代码对Queue vs deque的速度很敏感,那么Python可能不是您想要的语言。 (2认同)
  • @fantabolous尽管我之前的评论,但我不太了解您将如何使用`deque`进行通信。如果将“ pop”包装到“ try / except”中,则将导致一个繁忙的循环,仅在等待新数据时便会占用大量CPU。与`Queue`提供的阻塞调用相比,这似乎是一种极其低效的方法,后者确保等待数据的线程进入睡眠状态,并且不会浪费CPU时间。 (2认同)
  • 然后,您可能需要阅读Queue.Queue的源代码,因为它是使用`collections.deque`编写的:https://hg.python.org/cpython/file/2.7/Lib/Queue.py -它使用条件变量来有效地允许包装的'deque'在线程边界上安全有效地访问。源代码中有关于如何使用“双端队列”进行通信的说明。 (2认同)

Jon*_*han 39

如果您正在寻找的是一种在线程之间传输对象的线程安全方法,那么两者都可以工作(FIFO和LIFO都有).对于FIFO:

注意:

  • 其他操作deque可能不是线程安全的,我不确定.
  • deque不会阻止pop()或不能阻止popleft()消费者线程流阻塞直到新项目到达.

但是,deque似乎具有显着的效率优势.以下是使用CPython 2.7.3插入和删除100k项目的几秒内的基准测试结果

deque 0.0747888759791
Queue 1.60079066852
Run Code Online (Sandbox Code Playgroud)

这是基准代码:

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
Run Code Online (Sandbox Code Playgroud)

  • 好,谢谢.那阻止我使用双端队因为我认为你知道我没有的东西.我想我会假设它是线程安全的,直到我发现其他情况. (3认同)
  • Python 2 和 3 新版本的文档指出“双端队列支持线程安全、内存高效的从双端队列的任意一侧追加和弹出,在任一方向上具有大致相同的 O(1) 性能。” (2认同)

小智 7

有关信息,请参阅deque thread-safety引用的Python票证(https://bugs.python.org/issue15329).标题"澄清哪种deque方法是线程安全的"

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

deque的append(),appendleft(),pop(),popleft()和len(d)操作在CPython中是线程安全的.append方法最后有一个DECREF(对于设置了maxlen的情况),但是在完成所有结构更新并且不变量已经恢复之后会发生这种情况,因此可以将这些操作视为原子操作.

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


kxr*_*kxr 7

所有单元素方法deque都是原子和线程安全的。所有其他方法也是线程安全的。诸如len(dq),dq[4]产生瞬时正确值。但是考虑一下dq.extend(mylist)mylist当其他线程也在同一侧附加元素时,您无法保证所有元素都在一行中归档 - 但这通常不是线程间通信和有问题的任务的要求。

所以 adequeQueuedeque在引擎盖下使用 a )快约 20 倍,除非您不需要“舒适的”同步 API(阻塞/超时)、严格maxsize遵守或“覆盖这些方法(_put、_get、.. ) 来实现其他队列组织的子类化行为,或者当您自己处理这些事情时,deque对于高速线程间通信来说,裸机是一个很好且有效的交易。

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

  • 您能否发布列表或双端队列是线程安全的信息来源? (2认同)

小智 6

添加notify_all()到每个deque appendpopleft会导致deque比默认行为实现的 20 倍改进deque更糟糕的结果:

deque + notify_all: 0.469802
Queue:              0.667279
Run Code Online (Sandbox Code Playgroud)

@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)
Run Code Online (Sandbox Code Playgroud)

而且似乎性能受到此功能的限制condition.notify_all()

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