Queue.Queueとcollections.deque


181

複数のスレッドがデータを入れることができるキューが必要です。複数のスレッドがそこから読み取ることができます。

Pythonには少なくともQueue.Queueとcollections.dequeの2つのキュークラスがあり、前者は後者を内部的に使用しているようです。どちらもドキュメントではスレッドセーフであると主張しています。

ただし、キューのドキュメントにも次のように記載されています。

collections.dequeは、ロックを必要としない高速のアトミックなappend()およびpopleft()操作を備えた、無制限のキューの代替実装です。

私はまったく理解していないと思います:これは、結局、dequeが完全にスレッドセーフではないことを意味しますか?

もしそうなら、私は2つのクラスの違いを完全に理解していないかもしれません。Queueがブロッキング機能を追加していることがわかります。一方で、オペレーター内のサポートなど、いくつかの必要な機能が失われます。

内部の両端キューオブジェクトに直接アクセスするのは、

Queue()。dequeのx

スレッドセーフ?

また、dequeがすでにスレッドセーフであるのに、なぜQueueはその操作にミューテックスを使用するのですか?


RuntimeError: deque mutated during iteration取得できるのはdeque、いくつかのスレッド間で共有され、ロックされていないことです...
toine

4
スレッドとは何の関係もない@toine。このエラーdequeは、同じスレッドで繰り返し処理を行っているときにadd / deleteを実行すると発生します。このエラーが発生しない唯一の理由Queueは、Queue反復をサポートしていないことです。
2017

回答:


281

Queue.Queuecollections.dequeさまざまな目的を果たします。Queue.Queueは、さまざまなスレッドがキューに入れられたメッセージ/データを使用して通信できるようにすることを目的としていますcollections.dequeが、単にデータ構造として意図されています。だからこそQueue.Queueのようなメソッドを持っているput_nowait()get_nowait()join()のに対し、collections.dequeしません。Queue.Queueコレクションとして使用することを意図していないため、in演算子のようなものが欠けています。

つまり、複数のスレッドがあり、ロックを必要とせずにスレッドが通信できるようにしたい場合は、を探しQueue.Queueます。データ構造としてキューまたは両端キューが必要な場合は、を使用しますcollections.deque

最後に、内部の両端キューにアクセスして操作するQueue.Queueことは火で遊んでいます-あなたは本当にそれをしたくありません。


6
いいえ、それはまったく良い考えではありません。のソースを見ると、内部でQueue.Queue使用さdequeれています。collections.dequeはコレクションQueue.Queueですが、は通信メカニズムです。のオーバーヘッドQueue.Queueは、スレッドセーフにすることです。使用してdequeスレッド間の通信にのみ痛みを伴うレースにつながります。dequeスレッドセーフになったときはいつでも、それはインタープリターがどのように実装されているかについての偶然であり、信頼できるものではありません。それがQueue.Queueそもそも存在する理由です。
Keith Gaughan、2015

2
スレッド間で通信している場合は、dequeを使用して火で遊んでいることに注意してください。dequeはGILが存在するため、偶然スレッドセーフです。GILを使用しない実装では、パフォーマンス特性がまったく異なるため、他の実装を割り引くのは賢明ではありません。さらに、シングルスレッドでの単純なベンチマークの使用とは対照的に、スレッド間で使用するためにQueueとdequeの時間を測定しましたか?あなたのコードがある場合はその両端キューVSキューの速度に敏感な、Pythonはあなたが探している言語ではないかもしれません。
キースガガン、2015

3
@KeithGaughan deque is threadsafe by accident due to the existence of GIL; dequeスレッドセーフを確保するためにGILに依存していることは事実ですが、そうではありませんby accident。公式のPythonドキュメントには、deque pop*/ append*メソッドはスレッドセーフであると明記されています。したがって、有効なpython実装はすべて同じ保証を提供する必要があります(GILを使用しない実装では、GILなしでそれを行う方法を理解する必要があります)。これらの保証は安全に信頼できます。
2017

2
@fantabolous私の以前のコメントにもかかわらず、あなたがdequeコミュニケーションにどのように使用するかはよくわかりません。にラップpopするとtry/except、ビジーループが発生し、新しいデータを待つだけで大量のCPUが消費されます。これは、によって提供されるブロッキング呼び出しと比較して、ひどく非効率的なアプローチのように思われますQueue。これにより、データを待機しているスレッドがスリープ状態になり、CPU時間を無駄にしないことが保証されます。
2017

3
あなたは、ソースコードの読み取り撮りたいかもしれませんQueue.Queue、それが使用して書かれているので、その後のcollections.dequehg.python.org/cpython/file/2.7/Lib/Queue.pyを、それが効率的にできるように、条件変数を使用しています- dequeそれはアクセスするラップスレッドの境界を越えて安全かつ効率的に。dequeコミュニケーションにをどのように使用するかについての説明は、ソースにあります。
キースガガン

44

あなたが探しているすべてがスレッド間でオブジェクトを転送するスレッドセーフな方法である場合、両方が機能します(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

1
あなたは「他の操作はdequeスレッドセーフではないかもしれない」と主張しています。どこから手に入れましたか?
マット

@マット-私の意味をよりよく伝えるために言い換えられた
ジョナサン

3
わかりました。私が知らないことをあなたが知っていると思ったので、それが原因でdequeを使用できなくなりました。私は、別の方法で発見するまで、スレッドセーフであると思います。
マット14

@Matt "dequeのappend()、appendleft()、pop()、popleft()、およびlen(d)操作は、CPythonではスレッドセーフです。" 出典:bugs.python.org/issue15329
Filippo Vitale

7

詳細については、dequeスレッドセーフのために参照されるPythonチケットがありますhttps://bugs.python.org/issue15329)。タイトル「どのdequeメソッドがスレッドセーフかを明確にする」

ここでの結論https : //bugs.python.org/issue15329#msg199368

両端キューのappend()、appendleft()、pop()、popleft()、およびlen(d)操作は、CPythonではスレッドセーフです。追加メソッドの最後にはDECREFがあります(maxlenが設定されている場合)が、これはすべての構造体の更新が行われ、不変条件が復元された後で発生するため、これらの操作をアトミックとして処理しても問題ありません。

とにかく、100%確実ではなく、パフォーマンスよりも信頼性を好む場合は、ロックのようなものを入れてください;)


6

のすべての単一要素メソッドはdeque、アトミックでスレッドセーフです。他のすべてのメソッドもスレッドセーフです。のようなものはlen(dq)dq[4]瞬間的に正しい値をもたらします。しかし、たとえば、他のスレッドも同じ側に要素を追加する場合dq.extend(mylist)、すべての要素mylistが一列に並べられるという保証はありませんが、通常、スレッド間通信や問題のタスクでは必要ありません。

だからdequeである〜20倍速くよりQueue(使用するdequeフードの下)、あなたは「快適」の同期APIを必要としない場合を除き、厳密(タイムアウト/ブロック)maxsizeobeyanceまたは、「これらのメソッドをオーバーライドします(_put、_GETを... )他のキュー組織のサブクラス化動作を実装する場合、または自分でそのようなことを自分で処理する場合、ベアdequeスレッドは高速スレッド間通信に適した効率的な方法です。

実際、追加のミューテックスや追加のメソッド._get()などのメソッド呼び出しが頻繁に使用されるのは、Queue.py下位互換性の制約、過去の過剰設計、およびスレッド間通信におけるこの重要な速度のボトルネック問題に対する効率的なソリューションを提供するための注意の欠如が原因です。リストは古いバージョンのPythonで使用されていましたが、list.append()/。pop(0)でもアトミックでスレッドセーフです...


3

notify_all()それぞれにdeque appendを追加すると、デフォルトの動作で達成される20倍の改善よりもpopleftはるかに悪い結果にdequeなりますdeque

deque + notify_all: 0.469802
Queue:              0.667279

@ジョナサンは彼のコードを少し修正し、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」演算子を探している場合、デキューまたはキューは、問題にとって最も適切なデータ構造ではない可能性があります。


1
まあ、私がしたいことは、重複がキューに追加されていないことを確認することです。これはキューが潜在的にサポートできるものではありませんか?
miracle2k 2009

1
別のセットを用意し、キューから何かを追加/削除するときに更新するのがおそらく最善でしょう。これはO(n)ではなくO(log n)になりますが、セットとキューの同期を保つ(つまり、ロックする)ように注意する必要があります。
brian-brazil

Pythonセットはハッシュテーブルなので、O(1)になります。しかし、はい、まだロックを行う必要があります。
AFoglia 2012年

1

(コメントする評判がないようです...)異なるスレッドから使​​用する両端キューのメソッドに注意する必要があります。

deque.get()はスレッドセーフであるように見えますが、

for item in a_deque:
   process(item)

別のスレッドが同時に項目を追加している場合、失敗する可能性があります。「反復中にdequeが変更された」と文句を言うRuntimeExceptionを受け取りました。

collectionsmodule.cをチェックして、これによって影響を受ける操作を確認してください


この種のエラーは、スレッドや主要なスレッドセーフにとって特別なものではありません。それは、例えばただやることによって起こります >>> di = {1:None} >>> for x in di: del di[x]
kxr '19

1
基本的に、別のスレッドによって変更される可能性のあるものをループしないでください(場合によっては、独自の保護を追加することでそれを実行できます)。キューと同様に、アイテムを処理する前にキューからポップ/取得することを目的としており、通常はwhileループでそれを行います。
幻想的な
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.