web-dev-qa-db-ja.com

Python Multiprocessing.Pool遅延反復

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」のみを出力します。

誰かが何が起こっているのかを明確にできますか?プロセスプールにイタレータを必要に応じて「遅延」で評価させる何らかの方法がある場合は?

おかげで、

ガベ

60
Gabe

最初にプログラムの終わりを見てみましょう。

マルチプロセッシングモジュールは、プログラムの終了時に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_関数のputsのworkerinqueuesタスクとしてジェネレーターを完全に消費します。

つまり、すべての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)
_
33
unutbu

私もこの問題を抱えていて、マップがすべての要素を消費することを知ってがっかりしました。マルチプロセッシングで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」でテスト済み

4
GrantJ

あなたが欲しいものは、ウェブサイトからの NuMap パッケージに実装されています:

NuMapは、並列(スレッドベースまたはプロセスベース、ローカルまたはリモート)、バッファー付き、マルチタスク、itertools.imapまたはmultiprocessing.Pool.imap関数の置き換えです。 imapと同様に、シーケンスまたは反復可能な要素の関数を評価しますが、遅延して評価します。怠惰は、「stride」および「buffer」引数を介して調整できます。

4
letmaik