質問内容1:
def long_time_task(name):
print('Run task {} ({})...'.format(name, os.getpid()))
start = time.time()
time.sleep(3)
end = time.time()
print('Task {} runs {} seconds.'.format(name, (end - start)))
+def long_time_task(self, name):
...
...
とすると、なぜスルーされるのか?
回答:
1.引数不一致のTypeError
これは、まず 呼び出し側である、main()内の long_time_task()関数側を見てみると
for i in range(5):
p.apply_async(long_time_task, args=(i+1,))
となっていることから、long_time_task
関数に、「(i+1,)
」という1つの要素を持ったタプルを引数として渡していることがわかります。
一方スルーされる結果となった後半のコードでは
def long_time_task(self, name):
のように、2つのパラメータで受け取るようにしています。
このように、呼び出される側(引数を受け取る側)は受け取り口を2つ用意しているのに、呼び出し側は1つしか引数を与えていません。
これは引数の数が不一致のためTypeErrorという例外を発生させる事象となります。
2.multiprocessingの子プロセスの実行時例外
multiprocessingにおいて、Poolで生成した子プロセスの実行時に例外が発生しても、その例外は通常、出力されません(親側プロセスも子の例外を検出して止まったりしません)
上記で言えば、後半のコードの場合は、子プロセスであるlong_time_taskは、1.で言及した引数不一致のTypeErrorが発生してすぐ終了してしまっています。
しかしその例外が発生したことは一般的に見えている画面のどこにも出力されません。
これが何もエラーっぽいこともなく終わったように見えた原因です。
3.例外を捕捉するには
では、子の中でどんな例外がおきたか、どうやって捕捉すればいいのでしょうか。
Pool.apply_async() が返す結果のクラスである、AsyncResult
にget()という関数があり、これを利用することができます。
diff
1略
2+results = []
3
4def main():
5 print('Parent process {}.'.format(os.getpid()))
6
7 for i in range(5):
8 res = p.apply_async(long_time_task, args=(i+1,))
9+ results.append(res)
10
11 print('Waiting for all subprocesses done...')
12 p.close()
13 p.join()
14
15 print('All subprocesses done.')
16
17+ for res in results:
18+ res.get()
19
20略
4行追加しました(読みやすいようコメントは削除しています)
これは、apply_asyncの結果をresultsというリストに追加し、
全部子プロセスが終了した後、resultsの中の結果のget()を呼び出しています。
通常、このget()は、それぞれの子プロセスが走らせた関数の結果を返すために使うものですが、
子プロセス実行中に例外が発生していた場合は、get()の段階でその例外が送出され、親プロセスも止まります。
上記後段のコードにあてはめると、
text
1Parent process ****.
2Waiting for all subprocesses done...
3All subprocesses done.
4multiprocessing.pool.RemoteTraceback:
5"""
6Traceback (most recent call last):
7 File "C:\Python\Python3\lib\multiprocessing\pool.py", line 125, in worker
8 result = (True, func(*args, **kwds))
9TypeError: long_time_task() missing 1 required positional argument: 'name'
10"""
11
12The above exception was the direct cause of the following exception:
13
14Traceback (most recent call last):
15 File ".\test357269.py", line 34, in <module>
16 main()
17 File ".\test357269.py", line 31, in main
18 print(res.get())
19 File "C:\Python\Python3\lib\multiprocessing\pool.py", line 771, in get
20 raise self._value
21TypeError: long_time_task() missing 1 required positional argument: 'name'
という出力が出てすぐ止まります。
出力内容を見ると、子プロセス実行中の
TypeError: long_time_task() missing 1 required positional argument: 'name'
が原因で止まっていることが分かります。
質問内容2:
また関数にselfを使う際にはどうすればスルーされずに実行できるのか教えてください。
selfを使う、というのは、多分クラスのところでご覧になったのだと思いますが、今回の件は、クラスで使うselfとは無関係です。
単純に「1つだけ書くべきパラメータを2つにしてしまった」ことが原因で発生した事象に過ぎません。
selfではなく、例えば別の名前の変数を使っても、数が多すぎたり少なすぎたりするだけで、同じような事象になります。
def long_time_task(foo, name): # selfではない別の名前、数が多い(2個)
def long_time_task(baz, yoo, name): # 数がさらに多い(3個)
def long_time_task(): # 数が少なすぎる(0個)
逆に1.冒頭の後半のパターンで無理やり例外を生じさせないようにするなら、下記のようなコードになるでしょう。
diff
1def long_time_task(self, name): # selfの代わりにfooなど別の名前を使っても同じ。
2 print('Run task {} ({})...'.format(name, os.getpid()))
3 start = time.time()
4 time.sleep(3)
5 end = time.time()
6 print('Task {} runs {} seconds.'.format(name, (end - start)))
7
8results = []
9
10def main():
11 print('Parent process {}.'.format(os.getpid()))
12
13 for i in range(5):
14- res = p.apply_async(long_time_task, args=(i+1,))
15+ res = p.apply_async(long_time_task, args=(None,i+1))
16
17 print('Waiting for all subprocesses done...')
18 p.close()
19 p.join()
20
21 print('All subprocesses done.')
22
23if __name__ == '__main__':
24 main()
25
変えたのは、
diff
1+ res = p.apply_async(long_time_task, args=(None,i+1))
の部分です。
受け取り側がself, name
の2つなのですから、呼び出し側を1つの要素から2つの要素をもったタブルに変え、さらにname
は2番目なので、2番目のパラメータにi+1
を渡すようにします。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/09/01 16:16