Queue.Queue vs. collection.deque
Мне нужна очередь, в которую могут вставлять несколько потоков, и несколько потоков могут читать.
Python имеет как минимум два класса очереди Queue.Queue и collections.deque, причем первый, по-видимому, использует его внутри. Оба документа утверждают, что они являются потокобезопасными в документации.
Однако в документах очереди также указывается:
collections.deque - альтернатива реализация неограниченных очередей с быстрым атомом append() и операции popleft(), которые не требуют блокировки.
Я думаю, что я не совсем понимаю: означает ли это, что deque не полностью потокобезопасен?
Если это так, я могу не полностью понять разницу между этими двумя классами. Я вижу, что в Queue добавлены блокирующие функции. С другой стороны, он теряет некоторые функции deque, такие как поддержка для оператора.
Доступ к внутреннему объекту deque напрямую,
x в очереди(). deque
потокобезопасна?
Также, почему Queue использует мьютекс для его операций, когда deque уже потокобезопасен?
Ответы
Ответ 1
Queue.Queue
и collections.deque
служат для разных целей. Queue.Queue предназначен для передачи различным потокам сообщений с использованием сообщений/данных в очереди, тогда как collections.deque
просто предназначен как структура данных. Поэтому Queue.Queue
имеет такие методы, как put_nowait()
, get_nowait()
и join()
, тогда как collections.deque
нет. Queue.Queue
не предназначен для использования в качестве коллекции, поэтому ему не хватает операторов in
.
Это сводится к следующему: если у вас несколько потоков, и вы хотите, чтобы они могли общаться без необходимости блокировки, вы ищете Queue.Queue
; если вам просто нужна очередь или двойная очередь в качестве структуры данных, используйте collections.deque
.
Наконец, доступ и управление внутренним deque Queue.Queue
играет с огнем - вы действительно не хотите этого делать.
Ответ 2
Если все, что вы ищете, это потокобезопасный способ передачи объектов между потоками, то оба они будут работать (как для FIFO, так и для LIFO). Для FIFO:
Примечание:
- Другие операции над
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
Ответ 3
deque
является потокобезопасным. "операции, которые не требуют блокировки" означает, что вам не нужно делать блокировку самостоятельно, deque
позаботится об этом.
Взглянув на источник Queue
, внутренний deque называется self.queue
и использует мьютекс для аксессуаров и мутаций, поэтому Queue().queue
не является потокобезопасным для использования.
Если вы ищете оператора "in", то дека или очередь, возможно, не самая подходящая структура данных для вашей проблемы.
Ответ 4
Для информации имеется билет на Python, на который ссылаются на безопасность потока deque (https://bugs.python.org/issue15329).
Title "уточнить, какие методы deque являются потокобезопасными"
Нижняя строка здесь: https://bugs.python.org/issue15329#msg199368
Deque append(), appendleft(), pop(), popleft() и len (d) операции являются поточно-безопасными в CPython. Методы добавления имеют DECREF в конце (для случаев, когда maxlen установлен), но это происходит после того, как были сделаны все обновления структуры, и инварианты были восстановлены, так что нормально обрабатывать эти операции как атомный.
В любом случае, если вы не уверены на 100% и предпочитаете надёжность по производительности, просто поставьте как Lock;)
Ответ 5
(кажется, у меня нет репутации, чтобы комментировать...)
Вам нужно быть осторожным, какие методы детекса вы используете из разных потоков.
deque.get() кажется потокобезопасным, но я обнаружил, что выполнение
for item in a_deque:
process(item)
может завершиться ошибкой, если другой поток добавляет элементы одновременно.
Я получил исключение RuntimeException, которое жаловалось, что "deque мутировал во время итерации".
Отметьте collectionsmodule.c, чтобы увидеть, какие действия затронуты этим
Ответ 6
Все одноэлементные методы на deque
являются атомарными и потокобезопасными. Все остальные методы также являются потокобезопасными. Такие вещи, как len(dq)
, dq[4]
, дают мгновенные правильные значения. Но подумайте, например. about dq.extend(mylist)
: вы не получаете гарантию, что все элементы в mylist
подаются в строку, когда другие потоки также добавляют элементы на одной стороне - но это обычно не является обязательным требованием в межпоточной связи и для опрошенных задача.
Итак, deque
~ 20 раз быстрее, чем Queue
(который использует deque
под капотом), и если вам не нужен "удобный" интерфейс синхронизации (блокировка/тайм-аут), строгий maxsize
подчинение или "Переопределить эти методы (_put, _get,..), чтобы реализовать другие подсистемы очередей", или когда вы сами заботитесь о таких вещах, то голый deque
является хорошим и эффективным решением для высокоскоростная межпоточная связь.
Фактически интенсивное использование дополнительного метода mutex и дополнительного метода ._get()
и т.д. в Queue.py
вызвано ограничениями обратной совместимости, прошлым чрезмерным дизайном и отсутствием ухаживания за предоставление эффективного решения этой важной скорости проблема узких мест в межпоточной коммуникации. Список использовался в более старых версиях Python, но даже list.append()/. Pop (0) был и является атомарным и потокобезопасным...