PythonのMultiprocessing.Poolクラスがmap、imap、map_asyncでどのように機能するのか疑問に思っています。私の特定の問題は、メモリを多く使用するオブジェクトを作成するイテレータにマップしたいことと、これらのオブジェクトをすべて同時にメモリに生成したくないことです。さまざまなmap()関数がイテレータを乾燥させるか、または子プロセスがゆっくり進んだときにのみnext()関数をインテリジェントに呼び出すかどうかを確認したかったので、いくつかのテストを次のようにハッキングしました。
def g():
for el in xrange(100):
print el
yield el
def f(x):
time.sleep(1)
return x*x
if __name__ == '__main__':
pool = Pool(processes=4) # start 4 worker processes
go = g()
g2 = pool.imap(f, go)
g2.next()
マップ、imap、map_asyncなども同様です。ただし、これは最も重要な例です。g2でnext()を1回呼び出すだけで、すべての要素がジェネレーターg()から出力されます。 ()1回、したがって「1」のみを出力します。
誰かが何が起こっているのかを明確にできますか?プロセスプールにイタレータを必要に応じて「遅延」で評価させる何らかの方法がある場合は?
おかげで、
ガベ
最初にプログラムの終わりを見てみましょう。
マルチプロセッシングモジュールは、プログラムの終了時にatexit
を使用して_multiprocessing.util._exit_function
_を呼び出します。
g2.next()
を削除すると、プログラムはすぐに終了します。
__exit_function
_は最終的に_Pool._terminate_pool
_を呼び出します。メインスレッドは_pool._task_handler._state
_の状態をRUN
からTERMINATE
に変更します。一方、_pool._task_handler
_スレッドは_Pool._handle_tasks
_でループし、条件に到達するとベイルアウトします
_ if thread._state:
debug('task handler found thread._state != RUN')
break
_
(/usr/lib/python2.6/multiprocessing/pool.pyを参照)
これは、タスクハンドラーがジェネレーターg()
を完全に消費するのを阻止するものです。 _Pool._handle_tasks
_を見ると、
_ for i, task in enumerate(taskseq):
...
try:
put(task)
except IOError:
debug('could not put task on queue')
break
_
これは、ジェネレータを使用するコードです。 (taskseq
は厳密にはジェネレーターではありませんが、taskseq
が消費されるため、ジェネレーターも同様です。)
対照的に、g2.next()
を呼び出すと、メインスレッドは_IMapIterator.next
_を呼び出し、self._cond.wait(timeout)
に達すると待機します。
__exit_function
_を呼び出す代わりにメインスレッドが待機していることで、タスクハンドラースレッドを正常に実行できます。つまり、_Pool._handle_tasks
_関数のput
sのworker
のinqueue
sタスクとしてジェネレーターを完全に消費します。
つまり、すべてのPool
マップ関数は、指定されたイテラブル全体を消費します。ジェネレータをチャンクで消費したい場合は、代わりにこれを行うことができます:
_import multiprocessing as mp
import itertools
import time
def g():
for el in xrange(50):
print el
yield el
def f(x):
time.sleep(1)
return x * x
if __== '__main__':
pool = mp.Pool(processes=4) # start 4 worker processes
go = g()
result = []
N = 11
while True:
g2 = pool.map(f, itertools.islice(go, N))
if g2:
result.extend(g2)
time.sleep(1)
else:
break
print(result)
_
私もこの問題を抱えていて、マップがすべての要素を消費することを知ってがっかりしました。マルチプロセッシングでQueueデータ型を使用して、イテレータを遅延消費する関数をコーディングしました。これは、@ unutbuが彼の回答へのコメントで説明するものに似ていますが、彼が指摘するように、キューを再ロードするためのコールバックメカニズムがないことに悩まされています。 Queueデータ型は代わりにタイムアウトパラメータを公開し、効果を上げるために100ミリ秒を使用しました。
from multiprocessing import Process, Queue, cpu_count
from Queue import Full as QueueFull
from Queue import Empty as QueueEmpty
def worker(recvq, sendq):
for func, args in iter(recvq.get, None):
result = func(*args)
sendq.put(result)
def pool_imap_unordered(function, iterable, procs=cpu_count()):
# Create queues for sending/receiving items from iterable.
sendq = Queue(procs)
recvq = Queue()
# Start worker processes.
for rpt in xrange(procs):
Process(target=worker, args=(sendq, recvq)).start()
# Iterate iterable and communicate with worker processes.
send_len = 0
recv_len = 0
itr = iter(iterable)
try:
value = itr.next()
while True:
try:
sendq.put((function, value), True, 0.1)
send_len += 1
value = itr.next()
except QueueFull:
while True:
try:
result = recvq.get(False)
recv_len += 1
yield result
except QueueEmpty:
break
except StopIteration:
pass
# Collect all remaining results.
while recv_len < send_len:
result = recvq.get()
recv_len += 1
yield result
# Terminate worker processes.
for rpt in xrange(procs):
sendq.put(None)
このソリューションには、Pool.mapへのリクエストをバッチ処理しないという利点があります。一人の労働者が他の労働者の進歩を妨げることはできません。 YMMV。ワーカーの終了を通知するために別のオブジェクトを使用したい場合があることに注意してください。この例では、Noneを使用しています。
「Python 2.7(r27:82525、2010年7月4日、09:01:59)[MSC v.1500 32ビット(Intel)] on win32」でテスト済み
あなたが欲しいものは、ウェブサイトからの NuMap パッケージに実装されています:
NuMapは、並列(スレッドベースまたはプロセスベース、ローカルまたはリモート)、バッファー付き、マルチタスク、itertools.imapまたはmultiprocessing.Pool.imap関数の置き換えです。 imapと同様に、シーケンスまたは反復可能な要素の関数を評価しますが、遅延して評価します。怠惰は、「stride」および「buffer」引数を介して調整できます。