Queue.Queue vs. collections.deque


181

여러 스레드가 물건을 넣을 수 있고 여러 스레드가 읽을 수있는 대기열이 필요합니다.

파이썬에는 적어도 두 개의 큐 클래스 인 Queue.Queue와 collections.deque가 있으며, 전자는 후자를 내부적으로 사용합니다. 둘 다 설명서에서 스레드 안전하다고 주장합니다.

그러나 큐 문서에는 다음과 같은 상태가 있습니다.

collections.deque는 잠금이 필요없는 빠른 원자 append () 및 popleft () 작업을 사용하여 언 바운드 큐의 대체 구현입니다.

내가 이해하지 못하는 것 : 이것이 deque가 완전히 스레드 안전하지 않다는 것을 의미합니까?

그렇다면 두 클래스의 차이점을 완전히 이해하지 못할 수도 있습니다. 대기열에 차단 기능이 추가되어 있음을 알 수 있습니다. 반면에 작동 ​​자에 대한 지원과 같은 일부 deque 기능이 손실됩니다.

내부 deque 객체에 직접 액세스하는 것은

Queue ()에서 x

스레드 안전?

또한 큐가 이미 스레드 안전 상태 일 때 큐가 뮤텍스를 사용하여 작동하는 이유는 무엇입니까?


RuntimeError: deque mutated during iteration당신이 얻을 수있는 것은 deque여러 스레드간에 공유 를 사용하고 잠금이없는 것입니다 ...
toine

4
스레드와 관련이없는 @toine. deque동일한 스레드에서도 반복 하는 동안 추가 / 삭제를 할 때마다이 오류가 발생할 수 있습니다 . 이 오류를 얻을 수없는 유일한 이유 QueueQueue반복을 지원하지 않기 때문입니다.
최대

답변:


281

Queue.Queuecollections.deque다른 목적을 제공합니다. Queue.Queue는 다른 스레드가 대기중인 메시지 / 데이터를 사용하여 통신 할 수 있도록 collections.deque하는 것이 아니라 단순히 데이터 구조로 사용하기위한 것입니다. 그의 왜 Queue.Queue같은 방법을 가지고 put_nowait(), get_nowait()하고 join()있는 반면, collections.deque그렇지 않습니다. Queue.Queue컬렉션으로 사용하도록 의도되지 않았으므로 in연산자 와 같은 기능이 부족합니다 .

여러 스레드가 있고 잠금없이 통신 할 수있게하려면 다음을 찾으십시오 Queue.Queue. 큐 또는 이중 엔드 큐를 데이터 구조로 사용하려면을 사용하십시오 collections.deque.

마지막으로, 내부의 deque에 접근하고 조작하는 Queue.Queue것은 불을 피우고 있습니다-당신은 정말로 그렇게하고 싶지 않습니다.


6
아니요, 전혀 좋은 생각이 아닙니다. 의 소스를 보면 후드 아래에서 Queue.Queue사용 deque됩니다. 통신 메커니즘 collections.deque인 반면 컬렉션 Queue.Queue입니다. 오버 헤드는 Queue.Queue스레드로부터 안전하도록하는 것입니다. deque스레드 간 통신에 사용하면 고통스러운 경쟁으로 이어질 수 있습니다. deque스레드 안전성이 발생할 때마다 인터프리터가 어떻게 구현되는지에 대한 행복한 사고이며 의존 해야하는 것은 아닙니다 . 그것이 Queue.Queue처음에 존재하는 이유 입니다.
Keith Gaughan

2
스레드를 통해 통신하는 경우 deque를 사용하여 불을 가지고 노는 것을 명심하십시오. deque는 GIL의 존재 로 우연히 스레드 안전 합니다 . GIL이없는 구현은 완전히 다른 성능 특성을 가지므로 다른 구현을 할인하는 것이 현명하지 않습니다. 또한 단일 스레드에서의 사용에 대한 순진한 벤치 마크와 달리 여러 스레드에서 사용하기 위해 대기열 대 대기열을 시간 지정 했습니까? 코드가있는 경우 양단 큐 대 큐의 속도에 민감, 파이썬은 당신이 찾고있는 언어하지 않을 수 있습니다.
Keith Gaughan

3
@KeithGaughan deque is threadsafe by accident due to the existence of GIL; deque스레드 안전을 보장하기 위해 GIL에 의존 하는 것은 사실 이지만 그렇지 않습니다 by accident. 공식 파이썬 문서는 deque pop*/ append*메소드가 스레드 안전하다는 것을 분명히 밝힙니다 . 따라서 모든 유효한 파이썬 구현은 동일한 보증을 제공해야합니다 (GIL이없는 구현은 GIL없이이를 수행하는 방법을 알아야합니다). 그러한 보증에 안전하게 의존 할 수 있습니다.
최대

2
@fantabolous 내 이전 의견에도 불구하고, 나는 당신이 deque의사 소통에 어떻게 사용할 것인지 잘 모르겠습니다 . 당신이 포장하는 경우 poptry/except, 당신은 바쁜 루프 그냥 새로운 데이터를 기다리는 CPU의 엄청난 양을 먹는 종료됩니다. 이것은에서 제공하는 차단 호출과 비교할 때 끔찍하게 비효율적 인 접근 방식으로 보입니다. 이렇게 Queue하면 데이터를 기다리는 스레드가 CPU 시간을 낭비하지 않고 절전 모드로 전환됩니다.
최대

3
당신은 소스 코드의 읽기 수행 할 수 있습니다 Queue.Queue그것을 사용하여 작성 있기 때문에, 다음을 collections.deque: hg.python.org/cpython/file/2.7/Lib/Queue.py를 효율적으로 허용하도록 조건 변수를 사용 - deque이 액세스 할 수 있도록 랩 안전하고 효율적으로 스레드 경계를 넘어. deque커뮤니케이션에 사용 방법에 대한 설명 은 바로 소스에 있습니다.
Keith Gaughan

44

찾고있는 모든 것이 스레드간에 객체를 전송하는 스레드 안전 방법 이라면 둘 다 작동합니다 (FIFO 및 LIFO 모두). FIFO의 경우 :

노트 :

  • 에 대한 다른 작업은 deque스레드 안전하지 않을 수 있습니다.
  • deque에 차단하지 않습니다 pop()또는 popleft()새 항목이 도착할 때까지 차단에 소비자 스레드 흐름을 기반으로 할 수 있도록.

그러나 deque는 상당한 효율성 이점이있는 것으로 보입니다 . 다음은 100k 항목 삽입 및 제거에 CPython 2.7.3을 사용하는 몇 초 만에 벤치 마크 결과입니다.

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

1
"다른 작업은 deque스레드로부터 안전하지 않을 수 있습니다 "라고 주장합니다 . 어디서 구할 수 있습니까?
Matt

@ 매트는 - 더 나은 내 의미를 전달하기 위해 고쳐
조나단

3
알았어 고마워. 내가 당신이하지 않은 것을 알고 있다고 생각했기 때문에 deque를 사용하지 못하게되었습니다. 다른 방법을 발견 할 때까지 스레드 안전하다고 가정합니다.
Matt

@Matt "deque의 append (), appendleft (), pop (), popleft () 및 len (d) 연산은 CPython에서 스레드 안전합니다." 출처 : bugs.python.org/issue15329
Filippo Vitale

7

자세한 정보는 deque thread-safety ( https://bugs.python.org/issue15329 )에 대한 Python 티켓이 있습니다. 제목 "스레드에 안전한 deque 메소드를 명시하십시오"

결론은 다음과 같습니다. https://bugs.python.org/issue15329#msg199368

deque의 append (), appendleft (), pop (), popleft () 및 len (d) 연산은 CPython에서 스레드 안전합니다. 추가 메소드에는 끝에 maxCRES가 설정된 경우 DECREF가 있지만 모든 구조 업데이트가 수행되고 불변이 복원 된 후에 발생하므로 이러한 조작을 원자로 처리해도됩니다.

어쨌든 100 % 확실하지 않고 성능보다 신뢰성을 선호하는 경우 잠금 장치처럼 넣으십시오.)


6

모든 단일 요소 메소드 deque는 원자적이고 스레드로부터 안전합니다. 다른 모든 방법은 스레드로부터 안전합니다. 상황이 좋아 len(dq), dq[4]순간 정확한 값을 얻을 수 있습니다. 그러나 예를 들어 생각해보십시오 dq.extend(mylist): mylist다른 스레드가 같은면에 요소를 추가 할 때 모든 요소 가 연속적으로 줄 지어 있다는 보장을 얻지 못하지만 일반적으로 스레드 간 통신 및 문제가있는 작업의 요구 사항은 아닙니다.

따라서 a deque는 ~보다 20 배 빠릅니다 Queue( deque언더 후드 를 사용함 ). "편안한"동기화 API (차단 / 시간 초과), 엄격한 maxsize준수 또는 "이 메소드 무시 (_put, _get, .. ) 다른 큐 조직의 서브 클래 싱 동작 을 구현 하거나 직접 deque처리하는 경우 고속 스레드 간 통신에 적합하고 효율적입니다.

실제로 여분의 뮤텍스와 여분의 메소드 ._get()등의 메소드 호출 이 과도하게 사용되는 것은 Queue.py이전의 호환성 제한, 과거의 과도한 설계 및 스레드 간 통신 에서이 중요한 속도 병목 현상 문제에 대한 효율적인 솔루션을 제공하기위한주의가 부족하기 때문입니다. 목록은 이전 Python 버전에서 사용되었지만 list.append () /. pop (0)조차도 원자적이고 스레드 안전합니다 ...


3

추가 notify_all()deque appendpopleft에 대한 훨씬 더 나쁜 결과에 결과 deque보다 20 배 개선은 기본적으로 달성 deque행동 :

deque + notify_all: 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()

collections.deque는 잠금이 필요없는 빠른 원자 append () 및 popleft () 작업을 사용하여 언 바운드 큐의 대체 구현입니다. docs Queue


2

deque스레드 안전합니다. "잠금이 필요없는 작업"은 잠금을 직접 수행 할 필요가 없다는 것을 의미 deque합니다.

상기보고 촬영 Queue소스를 내부 양단 큐가 호출됩니다 self.queue그렇게하고, 접근 및 돌연변이에 대한 뮤텍스를 사용 Queue().queue한다 하지 스레드 안전 용도에 관한 것이다.

"in"연산자를 찾고 있다면 deque 또는 queue가 문제에 가장 적합한 데이터 구조가 아닐 수도 있습니다.


1
글쎄, 내가하고 싶은 것은 대기열에 중복이 추가되지 않도록하는 것입니다. 큐가 잠재적으로 지원할 수있는 것이 아닌가?
miracle2k

1
별도의 세트를 가지고 큐에서 무언가를 추가 / 제거 할 때 업데이트하는 것이 가장 좋습니다. 그것은 O (n)이 아니라 O (log n)이지만, 설정과 큐를 동기화 상태 (예 : 잠금)로 유지해야합니다.
brian-brazil

파이썬 세트는 해시 테이블이므로 O (1)이됩니다. 그러나 예, 여전히 잠금을 수행해야합니다.
AFoglia

1

(나는 언급 할 평판이 없다고 생각합니다 ...) 다른 스레드에서 사용하는 deque의 방법을주의해야합니다.

deque.get ()은 스레드로부터 안전한 것으로 보이지만

for item in a_deque:
   process(item)

다른 스레드가 동시에 항목을 추가하는 경우 실패 할 수 있습니다. "반복 중에 deque mutate"라는 불평을하는 RuntimeException이 발생했습니다.

collectionsmodule.c 를 점검 하여 이로 인해 영향을받는 작업을 확인하십시오.


이런 종류의 오류는 스레드와 주요 스레드 안전에 특별하지 않습니다. 예를 들어 >>> di = {1:None} >>> for x in di: del di[x]
kxr

1
기본적으로 다른 스레드가 수정 할 수있는 것을 반복해서는 안됩니다 (어떤 경우에는 자체 보호 기능을 추가하여 수행 할 수도 있음). 대기열과 마찬가지로 처리하기 전에 대기열에서 항목을 팝 / 가져 오기위한 것이므로 일반적으로 while루프로 수행합니다 .
환상의
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.