web-dev-qa-db-ja.com

Pythonマルチプロセッシングモジュールのapply()とapply_async()の違い

私は現在、次のように複数のプロセスを生成するコードを持っています。

_pool = Pool(processes=None)
results = [pool.apply(f, args=(arg1, arg2, arg3)) for arg3 in arg_list]
_

私の考えは、これにより、_processes=None_以降に使用可能なすべてのコアを使用して、作業がコア間で分割されるというものでした。ただし、 multiprocessing module docsPool.apply()メソッドのドキュメントには次のように書かれています。

Apply()組み込み関数と同等です。結果の準備ができるまでブロックされるため、apply_async()は作業を並行して実行するのに適しています。さらに、funcは、プールのワーカーの1つでのみ実行されます。

最初の質問:私はこれをはっきりと理解していません。 applyはどのように作業をワーカー間で分散し、_apply_async_が行うこととどのように異なりますか?タスクがワーカー間で分散される場合、funcがワーカーの1つでのみ実行される可能性はありますか?

私の推測:私の推測では、現在の実装では、applyが特定の引数のセットを使用してワーカーにタスクを与え、待機していると思いますそのワーカーを実行し、次の一連の引数を別のワーカーに渡します。このようにして、作業をさまざまなプロセスに送信していますが、並列処理は行われていませんapplyは実際には次のようになっているため、これが当てはまるようです。

_def apply(self, func, args=(), kwds={}):
    '''
    Equivalent of `func(*args, **kwds)`.
    Pool must be running.
    '''
    return self.apply_async(func, args, kwds).get()
_

2番目の質問:docs 、セクション16.6.1.5の紹介で、理由をよりよく理解したいと思います。 (「ワーカーのプールを使用する」)、[pool.apply_async(os.getpid, ()) for i in range(4)]mayなどの_apply_async_を使用した構造でも、より多くのプロセスを使用すると言われていますが、それは確かではありません。そうなる。 複数のプロセスを使用するかどうかを決定するものは何ですか?

6
Pietro Marchesi

あなたはPython2.7のドキュメントを指摘しているので、Python2.7マルチプロセッシングの実装に基づいて答えます。 Python3.Xでは異なる場合がありますが、それほど異なるものではありません。

applyapply_asyncの違い

これら2つの違いは、それらが実際にどのように実装されているかを見ると、本当に自己記述的です。ここでは、ボット関数のmultiprocessing/pool.pyからコードをコピーして貼り付けます。

def apply(self, func, args=(), kwds={}):
    '''
    Equivalent of `apply()` builtin
    '''
    assert self._state == RUN
    return self.apply_async(func, args, kwds).get()

ご覧のとおり、applyは実際にはapply_asyncを呼び出していますが、結果を返す直前にgetが呼び出されています。これにより、基本的に、結果が返されるまでapply_asyncブロックが作成されます。

def apply_async(self, func, args=(), kwds={}, callback=None):
    '''
    Asynchronous equivalent of `apply()` builtin
    '''
    assert self._state == RUN
    result = ApplyResult(self._cache, callback)
    self._taskqueue.put(([(result._job, None, func, args, kwds)], None))
    return result

apply_asyncは、タスクをタスクキューにエンキューし、送信されたタスクのhandleを返します。そのhandleを使用して、getまたはwaitを呼び出して、それぞれ結果を取得するか、タスクが終了するのを待つことができます。タスクが終了すると、タスクが返すものが引数としてcallback関数に渡されます。

例:

from multiprocessing import Pool
from time import sleep


def callback(a):
    print a


def worker(i, n):
    print 'Entering worker ', i
    sleep(n)
    print 'Exiting worker'
    return 'worker_response'


if __name__ == '__main__':
    pool = Pool(4)
    a = [pool.apply_async(worker, (i, 4), callback=callback) for i in range(8)]
    for i in a:
        i.wait()

結果:

Entering worker  0
Entering worker  1
Entering worker  2
Entering worker  3
Exiting worker
Exiting worker
Exiting worker
Exiting worker
Entering worker  4
Entering worker  5
worker_response
Entering worker  6
worker_response
Entering worker  7
worker_response
worker_response
Exiting worker
Exiting worker
Exiting worker
Exiting worker
worker_response
worker_response
worker_response
worker_response

apply_asyncを使用する場合は、結果を待つか、タスクが完了するのを待つ必要があることに注意してください。私の例の最後の2行にコメントを付けない場合、スクリプトは実行後すぐに終了します。

apply_asyncがより多くのプロセスを使用する理由

applyがどのように記述され、機能しているかに関して、私はこれを理解しています。 applyはタスクごとにタスクを実行してPoolの使用可能なプロセスに送信するため、apply_asyncはタスクをキューに追加し、タスクキュースレッドはそれらをPoolの使用可能なプロセスに送信します。これが、apply_asyncを使用するときに複数のプロセスが実行される可能性がある理由です。

著者が伝えようとした考えをよりよく理解するために、 this セクションを数回通過しました。ここで確認しましょう:

# evaluate "os.getpid()" asynchronously
res = pool.apply_async(os.getpid, ()) # runs in *only* one process
print res.get(timeout=1)              # prints the PID of that process

# launching multiple evaluations asynchronously *may* use more processes
multiple_results = [pool.apply_async(os.getpid, ()) for i in range(4)]
print [res.get(timeout=1) for res in multiple_results]

前の例を見て最後の例を理解しようとすると、apply_asyncの連続した呼び出しが複数ある場合、それは確かにmay同時にそれらの多くを実行する可能性があります。これはおそらく、その時点でPool内のプロセスがいくつ使用されているかによって異なります。それが彼らがmayと言う理由です。

3
ikac