web-dev-qa-db-ja.com

キューとPython

Pythonには、マルチプロセッシングモジュールの使用中に2種類のキューがあります。

  • キュー
  • JoinableQueue。

それらの違いは何ですか?

キュー

from multiprocessing import Queue
q = Queue()
q.put(item) # Put an item on the queue
item = q.get() # Get an item from the queue

JoinableQueue

from multiprocessing import JoinableQueue
q = JoinableQueue()
q.task_done() # Signal task completion
q.join() # Wait for completion
19
axcelenator

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()を呼び出す必要があります。そうしないと、未完了のタスクの数をカウントするために使用されるセマフォが最終的にオーバーフローし、例外が発生します。

18
fferri

ドキュメントに基づいて、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であることを確認します。

1
Ben