複数のスレッドが何かを入れることができ、複数のスレッドが読み込むことができるキューが必要です。
Pythonには少なくともQueue.Queueとcollections.dequeの2つのキュークラスがあり、前者は後者を内部的に使用しているようです。どちらもドキュメントではスレッドセーフであると主張しています。
ただし、キュードキュメントには次のようにも記載されています。
collections.dequeは、ロックを必要としない高速アトミックappend()およびpopleft()操作を備えた無制限キューの代替実装です。
私はまったく理解していないと思います:これは、dequeが結局完全にスレッドセーフではないことを意味しますか?
そうであれば、2つのクラスの違いを完全に理解できない可能性があります。 Queueがブロッキング機能を追加していることがわかります。一方、操作中のサポートのようないくつかのdeque機能は失われます。
内部dequeオブジェクトに直接アクセスするには、
x in Queue()。deque
スレッドセーフ?
また、dequeがすでにスレッドセーフであるときに、Queueがその操作にミューテックスを使用するのはなぜですか?
_Queue.Queue
_と_collections.deque
_は異なる目的を果たします。 Queue.Queueは、キューに入れられたメッセージ/データを使用してさまざまなスレッドが通信できるようにすることを目的としていますが、_collections.deque
_は単にデータ構造を目的としています。 _Queue.Queue
_にはput_nowait()
、get_nowait()
、およびjoin()
のようなメソッドがありますが、_collections.deque
_にはありません。 _Queue.Queue
_はコレクションとして使用することを目的としていないため、in
演算子のようなものが欠けています。
つまり、複数のスレッドがあり、ロックを必要とせずに通信できるようにしたい場合は、_Queue.Queue
_を探しています。データ構造としてキューまたはダブルエンドキューが必要な場合は、_collections.deque
_を使用します。
最後に、_Queue.Queue
_の内部dequeにアクセスして操作するのは火で遊んでいます-あなたは本当にそれをしたくありません。
あなたが探しているのがスレッド間でオブジェクトを転送するスレッドセーフな方法である場合、両方が機能します(FIFO and LIFO)。FIFOの場合:
注意:
deque
に対する他の操作はスレッドセーフではないかもしれませんが、わかりません。deque
はpop()
またはpopleft()
でブロックしないので、新しいアイテムが到着するまで、ブロックに基づいてコンシューマスレッドフローを作成することはできません。ただし、dequeには大幅な効率上の利点があるようです。 10万個のアイテムの挿入と削除に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
詳細については、Python dequeスレッドセーフ用に参照されるチケット( https://bugs.python.org/issue15329 )です。スレッドセーフ」
ここの一番下の行: https://bugs.python.org/issue15329#msg199368
Dequeのappend()、appendleft()、pop()、popleft()、およびlen(d)操作は、CPythonではスレッドセーフです。 appendメソッドの最後にはDECREFがあります(maxlenが設定されている場合)が、これはすべての構造の更新が行われ、不変式が復元された後に発生するため、これらの操作をアトミックとして扱うことは問題ありません。
とにかく、100%確信が持てず、パフォーマンスよりも信頼性を好む場合は、Lockのように入力してください;)
deque
のすべての単一要素メソッドは、アトミックでスレッドセーフです。他のすべてのメソッドもスレッドセーフです。 len(dq)
、_dq[4]
_のようなものは、一時的に正しい値を生成します。しかし、例えばdq.extend(mylist)
について:他のスレッドも同じ側に要素を追加する場合、mylist
のすべての要素が連続してファイルされるという保証はありませんが、通常interの要件ではありません-スレッド通信および問題のあるタスク用。
したがって、deque
はQueue
(フードの下でdeque
を使用)よりも20倍高速であり、「快適な」同期API(ブロッキング/タイムアウト)を必要としない限り)、厳密なmaxsize
順守または「これらのメソッド(_put、_get、..)をオーバーライドして他のキュー組織を実装する」サブクラス化動作、またはそのような場合は自分で物事を行う場合、裸のdeque
は、高速なスレッド間通信のための優れた効率的な取引です。
実際、_Queue.py
_で余分なmutexおよび余分なメソッド._get()
などのメソッド呼び出しが頻繁に使用されるのは、後方互換性の制約、過去の過剰設計、およびスレッド間通信におけるこの重要な速度のボトルネックの問題。リストは古いPythonバージョンで使用されていましたが、list.append()/。pop(0)でさえもアトミックでスレッドセーフです...
_deque 0.469802
Queue 0.667279
_
@Jonathanは彼のコードを少し修正し、cPython 3.6.2を使用してベンチマークを取得し、dequeループに条件を追加して、Queueの動作をシミュレートします。
_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()操作を備えた無制限キューの代替実装です。 ドキュメントキュー
(私はコメントする評判がないようです...)あなたは異なるスレッドから使用するdequeのメソッドに注意する必要があります。
deque.get()はスレッドセーフであるように見えますが、
for item in a_deque:
process(item)
別のスレッドが同時にアイテムを追加している場合、失敗する可能性があります。 「deque mutation during iteration」と文句を言うRuntimeExceptionを受け取りました。
collectionsmodule.c をチェックして、この操作の影響を受ける操作を確認します
deque
はスレッドセーフです。 「ロックを必要としない操作」とは、ロックを自分で行う必要がないことを意味し、deque
がそれを処理します。
Queue
ソースを見ると、内部両端キューは_self.queue
_と呼ばれ、アクセサとミューテーションにミューテックスを使用するため、Queue().queue
はnot threadです-安全に使用できます。
「in」演算子を探している場合、dequeまたはキューは問題に最適なデータ構造ではない可能性があります。