マルチプロセッシングプールのapply_syncメソッドを使用するときに、舞台裏で何が起こっているのかを少し理解しようとしています。
コールバックメソッドを実行するのは誰ですか? apply_asyncを呼び出したのはメインプロセスですか?
コールバック付きの一連のapply_asyncコマンドを送信して、プログラムを続行するとします。私のプログラムは、apply_asyncの最初から最後までまだ動作しています。メインプロセスがスクリプトでビジー状態のときに、コールバックはどのようにして「メインプロセス」を実行しますか?
ここに例があります。
import multiprocessing
import time
def callback(x):
print '{} running callback with arg {}'.format(multiprocessing.current_process().name, x)
def func(x):
print '{} running func with arg {}'.format(multiprocessing.current_process().name, x)
return x
pool = multiprocessing.Pool()
args = range(20)
for a in args:
pool.apply_async(func, (a,), callback=callback)
print '{} going to sleep for a minute'.format(multiprocessing.current_process().name)
t0 = time.time()
while time.time() - t0 < 60:
pass
print 'Finished with the script'
出力は次のようなものです
Arg 0でfuncを実行しているPoolWorker-1
Arg 1でfuncを実行するPoolWorker-2
Arg 2でfuncを実行するPoolWorker-3
MainProcessは1分間スリープします<-メインプロセスはビジーです
Arg 3でfuncを実行するPoolWorker-4
Arg 4でfuncを実行するPoolWorker-1
Arg 5でfuncを実行するPoolWorker-2
Arg 6でfuncを実行するPoolWorker-3
Arg 7でfuncを実行するPoolWorker-4
Arg 0でMainProcess実行中のコールバック<-メインプロセスが実行中のコールバックがwhileループ中にある!!
引数1のMainProcess実行コールバック
引数2のMainProcess実行コールバック
引数3のMainProcess実行コールバック
引数4でMainProcess実行コールバック
Arg 8でfuncを実行するPoolWorker-1
...
スクリプトで終了
MainProcessは、whileループの途中でコールバックをどのように実行していますか?
multiprocessing.Pool のドキュメントのコールバックに関するこのステートメントはヒントのように見えますが、理解できません。
apply_async(func [、args [、kwds [、callback]]])
結果オブジェクトを返すapply()メソッドのバリアント。
コールバックが指定されている場合、単一の引数を受け入れる呼び出し可能オブジェクトである必要があります。結果が準備完了になると、コールバックが適用されます(呼び出しが失敗した場合を除く)。そうしないと、結果を処理するスレッドがブロックされるため、コールバックはすぐに完了する必要があります。
確かにドキュメントにヒントがあります:
それ以外の場合は結果を処理するスレッドがブロックされるため、コールバックはすぐに完了するはずです。
コールバックはメインプロセスで処理されますが、それらは独自の個別のスレッドで実行されます。 Pool
を作成すると、実際にはいくつかのThread
オブジェクトが内部的に作成されます。
class Pool(object):
Process = Process
def __init__(self, processes=None, initializer=None, initargs=(),
maxtasksperchild=None):
self._setup_queues()
self._taskqueue = Queue.Queue()
self._cache = {}
... # stuff we don't care about
self._worker_handler = threading.Thread(
target=Pool._handle_workers,
args=(self, )
)
self._worker_handler.daemon = True
self._worker_handler._state = RUN
self._worker_handler.start()
self._task_handler = threading.Thread(
target=Pool._handle_tasks,
args=(self._taskqueue, self._quick_put, self._outqueue,
self._pool, self._cache)
)
self._task_handler.daemon = True
self._task_handler._state = RUN
self._task_handler.start()
self._result_handler = threading.Thread(
target=Pool._handle_results,
args=(self._outqueue, self._quick_get, self._cache)
)
self._result_handler.daemon = True
self._result_handler._state = RUN
self._result_handler.start()
私たちにとって興味深いスレッドは_result_handler
です。その理由については後ほど説明します。
ギアを1秒間切り替えて、apply_async
を実行すると、ApplyResult
オブジェクトが内部で作成され、子からの結果の取得が管理されます。
def apply_async(self, func, args=(), kwds={}, callback=None):
assert self._state == RUN
result = ApplyResult(self._cache, callback)
self._taskqueue.put(([(result._job, None, func, args, kwds)], None))
return result
class ApplyResult(object):
def __init__(self, cache, callback):
self._cond = threading.Condition(threading.Lock())
self._job = job_counter.next()
self._cache = cache
self._ready = False
self._callback = callback
cache[self._job] = self
def _set(self, i, obj):
self._success, self._value = obj
if self._callback and self._success:
self._callback(self._value)
self._cond.acquire()
try:
self._ready = True
self._cond.notify()
finally:
self._cond.release()
del self._cache[self._job]
ご覧のとおり、_set
メソッドは、タスクが成功したと仮定して、渡されたcallback
を実際に実行するメソッドです。また、__init__
の末尾のグローバルcache
辞書に自分自身を追加することにも注意してください。
次に、_result_handler
スレッドオブジェクトに戻ります。そのオブジェクトは、次のような_handle_results
関数を呼び出します。
while 1:
try:
task = get()
except (IOError, EOFError):
debug('result handler got EOFError/IOError -- exiting')
return
if thread._state:
assert thread._state == TERMINATE
debug('result handler found thread._state=TERMINATE')
break
if task is None:
debug('result handler got sentinel')
break
job, i, obj = task
try:
cache[job]._set(i, obj) # Here is _set (and therefore our callback) being called!
except KeyError:
pass
# More stuff
これは、子からの結果をキューから取り出し、cache
でそのエントリを見つけ、_set
を呼び出してコールバックを実行するループです。メインスレッドで実行されていないため、ループ状態であっても実行できます。