Очередь.Очередь против коллекций.двусторонняя очередь

Мне нужна очередь, в которую несколько потоков могут помещать материал, и несколько потоков могут читать.

Python имеет как минимум два класса очереди, Queue.Очередь и коллекции.дек, причем первый, по-видимому, использует второй внутренне. Оба утверждают, что являются потокобезопасными в документации.

однако в документах очереди также указано:

коллекций.дека является альтернативой реализация неограниченных очередей с помощью fast atomic append() и popleft() операции, которые не требуется блокировка.

что, я думаю, я не совсем понимаю: означает ли это, что deque не полностью потокобезопасен в конце концов?

Если это так, я не могу полностью понять разницу между двумя классами. Я вижу, что очередь добавляет функциональность блокировки. С другой стороны, он теряет некоторые функции deque, такие как поддержка in-operator.

доступ к внутреннему объекту deque напрямую, это

x в Очередь.)(дек!--1-->

потокобезопасным?

кроме того, почему очередь использует мьютекс для своих операций, когда deque уже потокобезопасен?

7 ответов


Queue.Queue и collections.deque служат для разных целей. Очередь.Очередь предназначена для обеспечения взаимодействия различных потоков с помощью сообщений/данных в очереди, тогда как collections.deque просто предназначен как datastructure. Вот почему Queue.Queue имеет такие методы, как put_nowait(), get_nowait() и join(), а collections.deque нет. Queue.Queue не предназначен для использования в качестве коллекции, поэтому ему не хватает подобных in оператора.

это сводится к следующему: если у вас есть несколько потоков, и вы хотите, чтобы они могли общаться без необходимости блокировки, вы ищете Queue.Queue; Если вы просто хотите очередь или двусторонняя очередь в качестве структуры данных, используйте collections.deque.

наконец, доступ и манипулирование внутренним Деком Queue.Queue играет с огнем - вы действительно не хотите этого делать.


если все, что вы ищете-это потокобезопасный способ передачи объектов между потоками, тогда оба будут работать (как для FIFO, так и для LIFO). ФИФО:

Примечание:

  • прочие операции на 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

для информации есть билет Python, на который ссылается deque thread-safety (https://bugs.python.org/issue15329). Название "уточните, какие методы deque являются потокобезопасными"

итог здесь:https://bugs.python.org/issue15329#msg199368

двухсторонняя очередь это функции append(), appendleft(), поп(), popleft () и лен(д) операции потокобезопасны в CPython. Методы append имеют DECREF в конце (для случаев, когда maxlen имеет было установлено), но это происходит после всех обновления структуры и инварианты были восстановлены, поэтому можно обрабатывать эти операции как атомный.

в любом случае, если вы не уверены на 100% и предпочитаете надежность производительности, просто поставьте блокировку ;)


deque является потокобезопасным. "операции, которые не требуют блокировки" означает, что вы не должны делать блокировку самостоятельно,deque позаботится об этом.

на Queue источник, внутренний deque называется self.queue и использует мьютекс для аксессоров и мутаций, так что Queue().queue is не thread-safe для использования.

если вы ищете оператор "in" , то deque или queue, возможно, не самая подходящая структура данных для вашего проблема.


deque 0.469802
Queue 0.667279

@Jonathan немного модифицирует свой код, и я получаю тест с помощью cPython 3.6.2 и добавляю условие в цикл deque для имитации очереди поведения.

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-это альтернативная реализация неограниченных очередей с операциями fast atomic append() и popleft (), которые не требуют блокировки. очереди документы


(Кажется, у меня нет репутации, чтобы комментировать...) Вы должны быть осторожны, какие методы deque вы используете из разных потоков.

deque.get () кажется threadsafe, но я обнаружил, что делаю

for item in a_deque:
   process(item)

может произойти сбой, если другой поток добавляет элементы одновременно. Я получил RuntimeException, который жаловался "deque мутировал во время итерации".

Регистрация collectionsmodule.c чтобы увидеть, какие операции будут затронуты этим


все одноэлементные методы на deque атомная и потокобезопасным. Все остальные методы также потокобезопасны. Такие вещи, как len(dq), dq[4] дайте мгновенные правильные значения. Но подумайте, например, о dq.extend(mylist): вы не получаете гарантию того, что все элементы mylist хранятся в строке, когда другие потоки также добавляют элементы на той же стороне - но это обычно не требование в межпотоковой связи и для поставленной задачи.

так deque ~20x быстрее, чем Queue (который использует deque под капотом) и если вам не нужен" удобный " API синхронизации (блокировка / тайм-аут), строгий maxsize подчинение или "переопределить эти методы (_put, запросе, ..) реализовать другие организации очереди" поведение подкласса, или когда вы заботитесь о таких вещах сами, тогда голый deque хорошее и эффективное дело для высокоскоростного сообщения Интер-потока.

на самом деле тяжелое использование дополнительного мьютекса и дополнительный метод ._get() etc. метод вызывает Queue.py из-за ограничений обратной совместимости, прошлого чрезмерного дизайна и отсутствия заботы о предоставлении эффективного решения этой важной проблемы узкого места скорости в межпотоковой связи. Список использовался в более старых версиях Python - но даже список.добавлять./)(pop (0) был и является атомарным и threadsafe ...