Pythonには、マルチプロセッシングモジュールの使用中に2種類のキューがあります。
それらの違いは何ですか?
from multiprocessing import Queue
q = Queue()
q.put(item) # Put an item on the queue
item = q.get() # Get an item from the queue
from multiprocessing import JoinableQueue
q = JoinableQueue()
q.task_done() # Signal task completion
q.join() # Wait for completion
JoinableQueue
にはメソッドjoin()
とtask_done()
がありますが、 Queue
にはありません。
class multiprocessing.Queue([maxsize])
パイプといくつかのロック/セマフォを使用して実装されたプロセス共有キューを返します。プロセスが最初にアイテムをキューに入れると、オブジェクトをバッファーからパイプに転送するフィーダースレッドが開始されます。
標準ライブラリのQueueモジュールからの通常のQueue.EmptyおよびQueue.Full例外は、タイムアウトを通知するために発生します。
Queueは、task_done()およびjoin()を除いて、Queue.Queueのすべてのメソッドを実装します。
class multiprocessing.JoinableQueue([maxsize])
QueueサブクラスであるJoinableQueueは、task_done()メソッドとjoin()メソッドが追加されたキューです。
task_done()
以前にエンキューされたタスクが完了したことを示します。キューコンシューマスレッドによって使用されます。タスクのフェッチに使用されるget()ごとに、後続のtask_done()の呼び出しは、タスクの処理が完了したことをキューに通知します。
Join()が現在ブロックされている場合、すべてのアイテムが処理されると再開します(つまり、キューに入れられたすべてのアイテムに対してtask_done()呼び出しが受信されました)。
キューにアイテムが配置された回数よりも多く呼び出された場合、ValueErrorを発生させます。
join()
キュー内のすべてのアイテムが取得および処理されるまでブロックします。
アイテムがキューに追加されるたびに、未完了のタスクの数が増えます。コンシューマースレッドがtask_done()を呼び出してアイテムが取得され、そのアイテムに対するすべての作業が完了したことを示すと、カウントは減少します。未完了のタスクの数がゼロになると、join()はブロックを解除します。
JoinableQueue
を使用する場合、キューから削除される各タスクに対してJoinableQueue.task_done()
を呼び出す必要があります。そうしないと、未完了のタスクの数をカウントするために使用されるセマフォが最終的にオーバーフローし、例外が発生します。
ドキュメントに基づいて、Queue
が実際に空であることを確認するのは困難です。 JoinableQueue
を使用すると、q.join()
を呼び出してキューが空になるのを待つことができます。各バッチの最後に個別に何かを行う個別のバッチで作業を完了したい場合は、これが役立ちます。
たとえば、キューを介して一度に1000のアイテムを処理し、別のバッチを完了したことをプッシュ通知をユーザーに送信するとします。これは通常のQueue
で実装するのは難しいでしょう。
次のようになります。
_import multiprocessing as mp
BATCH_SIZE = 1000
STOP_VALUE = 'STOP'
def consume(q):
for item in iter(q.get, STOP_VALUE):
try:
process(item)
# Be very defensive about errors since they can corrupt pipes.
except Exception as e:
logger.error(e)
finally:
q.task_done()
q = mp.JoinableQueue()
with mp.Pool() as pool:
# Pull items off queue as fast as we can whenever they're ready.
for _ in range(mp.cpu_count()):
pool.apply_async(consume, q)
for i in range(0, len(URLS), BATCH_SIZE):
# Put `BATCH_SIZE` items in queue asynchronously.
pool.map_async(expensive_func, URLS[i:i+BATCH_SIZE], callback=q.put)
# Wait for the queue to empty.
q.join()
notify_users()
# Stop the consumers so we can exit cleanly.
for _ in range(mp.cpu_count()):
q.put(STOP_VALUE)
_
注:実際にはこのコードを実行していません。アイテムを置くよりも早くキューから取り出すと、早く終了する可能性があります。その場合、このコードは更新を送信しますAT LEAST 1000アイテムごと、そしておそらくもっと頻繁です。進捗状況の更新の場合、おそらくそれで問題ありません。正確に1000であることが重要な場合は、mp.Value('i', 0)
をチェックし、join
がリリースされるたびに1000であることを確認します。