インスタンスメソッドでapply_async
を呼び出してその結果を取得する場合、行われた変更は、フォークされたプロセスの一部のままであることを期待していました。ただし、apply_asyncを呼び出すたびに、そのインスタンスの新しいコピーが作成されるようです。
次のコードを見てください。
from multiprocessing.pool import Pool
class Multitest:
def __init__(self):
self.i = 0
def run(self):
with Pool(2) as pool:
worker_jobs = []
for j in range(10):
job = pool.apply_async(self.process, (j,))
worker_jobs.append(job)
for job in worker_jobs:
res = job.get()
print("input", res)
def process(self, inp):
print("i", self.i)
self.i += 1
return inp
if __name__ == '__main__':
mt = Multitest()
mt.run()
出力例:
i 0
i 0
i 0
i 0
i 0
input 0
i 0
i 0
i 0
i 0
i 0
input 1
input 2
input 3
input 4
input 5
input 6
input 7
input 8
input 9
しかし、10個の入力が分散される2つのコアがあるため、i
プロパティが増加することを期待していました。
私は次のフローを期待していました:
run()
を呼び出しますapply_async
)を初期化することにより、i = 0
の作業をプールに分散します。process()
が新しいプロセスで何度も呼び出されます(range()
が使い果たされるまで)。プロセスを呼び出すたびに、そのプロセスのself.i
がインクリメントされます注:私はnotで、2つのプロセス間で共有状態を尋ねています。代わりに、単一のプロセスのクラスインスタンスが変更されない理由を尋ねています(なぜ、個々のプロセスのself.i
がインクリメントされないのですか)。
しかし、私はこの行動を見ていません。代わりに、出力された出力はゼロのみで、私の期待が間違っていたことを示しています。状態(プロパティi
)は維持されませんが、apply_async
を呼び出すたびに新しいインスタンス(または少なくとも新しいコピー)が作成されます。ここで何が欠けていますか、そしてこれを期待どおりに機能させるにはどうすればよいですか? (できればapply_async
を使うのが望ましいですが、結果の順序は維持する必要があります。)
私の知る限り、この動作はapply_async
だけでなく、他のpool
メソッドにも固有です。 なぜこれが起こるのか、そして方法の振る舞いが私が達成したい行動に変わりました。バウンティは、両方のクエリに回答を提供できる回答に行きます。
次のことが起こっていると思います:
self.process
が呼び出され、メソッドがシリアル化(ピクル化)され、子プロセスに送信されます。毎回新しいコピーが作成されます。子プロセスにはMultitest
の独自のインスタンスがないことに注意してください。これは、__name__ == '__main__'
プールによって作成されたフォークには適用されません。
子プロセスの状態を維持したい場合は、グローバル変数を使用して行うことができます。そのような変数を初期化するプールを作成するときに、初期化引数を渡すことができます。
以下は、意図したものの作業バージョンを示しています(ただし、OOPがないため、マルチプロセッシングではうまく機能しません)。
from multiprocessing.pool import Pool
def initialize():
global I
I = 0
def process(inp):
global I
print("I", I)
I += 1
return inp
if __name__ == '__main__':
with Pool(2, initializer=initialize) as pool:
worker_jobs = []
for j in range(10):
job = pool.apply_async(process, (j,))
worker_jobs.append(job)
for job in worker_jobs:
res = job.get()
print("input", res)
マルチプロセッシングとスレッド化の1つの違いは、プロセスが作成された後、そのプロセスが使用するメモリは親プロセスから仮想的に複製されるため、プロセス間で共有メモリが存在しないことです。
次に例を示します。
import os
import time
from threading import Thread
global_counter = 0
def my_thread():
global global_counter
print("in thread, global_counter is %r, add one." % global_counter)
global_counter += 1
def test_thread():
global global_counter
th = Thread(target=my_thread)
th.start()
th.join()
print("in parent, child thread joined, global_counter is %r now." % global_counter)
def test_fork():
global global_counter
pid = os.fork()
if pid == 0:
print("in child process, global_counter is %r, add one." % global_counter)
global_counter += 1
exit()
time.sleep(1)
print("in parent, child process died, global_counter is still %r." % global_counter)
def main():
test_thread()
test_fork()
if __name__ == "__main__":
main()
出力:
in thread, global_counter is 0, add one.
in parent, child thread joined, global_counter is 1 now.
in child process, global_counter is 1, add one.
in parent, child process died, global_counter is still 1.
あなたの場合:
for j in range(10):
# Before fork, self.i is 0, fork() dups memory, so the variable is not shared to the child.
job = pool.apply_async(self.process, (j,))
# After job finishes, child's self.i is 1 (not parent's), this variable is freed after child dies.
worker_jobs.append(job)
Python3では、バインドされたメソッドのpickle化にはオブジェクト自体も含まれ、基本的にそれを複製します。したがって、毎回apply_async
が呼び出され、オブジェクトself
もピクルされます。
import os
from multiprocessing.pool import Pool
import pickle
class Multitest:
def __init__(self):
self.i = "myattr"
def run(self):
with Pool(2) as pool:
worker_jobs = []
for j in range(10):
job = pool.apply_async(self.process, (j,))
worker_jobs.append(job)
for job in worker_jobs:
res = job.get()
print("input", res)
def process(self, inp):
print("i", self.i)
self.i += "|append"
return inp
def test_pickle():
m = Multitest()
print("original instance is %r" % m)
pickled_method = pickle.dumps(m.process)
assert b"myattr" in pickled_method
unpickled_method = pickle.loads(pickled_method)
# get instance from it's method (python 3)
print("pickle duplicates the instance, new instance is %r" % unpickled_method.__self__)
if __name__ == '__main__':
test_pickle()
出力:
original instance is <__main__.Multitest object at 0x1072828d0>
pickle duplicates the instance, new instance is <__main__.Multitest object at 0x107283110>