[python] Queue.Queue vs. collections.deque

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

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

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

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

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

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

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

Queue ()에서 x

스레드 안전?

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



답변

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것은 불을 피우고 있습니다-당신은 정말로 그렇게하고 싶지 않습니다.


답변

찾고있는 모든 것이 스레드간에 객체를 전송하는 스레드 안전 방법 이라면 둘 다 작동합니다 (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


답변

자세한 정보는 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 % 확실하지 않고 성능보다 신뢰성을 선호하는 경우 잠금 장치처럼 넣으십시오.)


답변

모든 단일 요소 메소드 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)조차도 원자적이고 스레드 안전합니다 …


답변

추가 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


답변

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

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

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


답변

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

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

for item in a_deque:
   process(item)

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

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