Pythonで並列処理などのためにmultiprocessing
を使うが、multiprocessing
で子プロセスを生成した時の挙動は、通常の関数呼び出しの挙動と異なる点がいくつかある。
準備
今回は簡単のため関数sleep_bug()
を用いる。これは実行中に途中でわざとエラーを発生させるために, i==5
の時に1/0
を実行しエラーを発生するようにした関数である。
import time
def sleep_bug():
for i in range(10):
print('sleeping %d' % i)
if i == 5:
1/0
time.sleep(1)
return i
sleep_bug()
# output
'''
sleeping 0
sleeping 1
sleeping 2
sleeping 3
sleeping 4
sleeping 5
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
<ipython-input-44-d9f02a4cf7f3> in <module>
----> 1 sleep_bug()
<ipython-input-41-26bb27998e63> in sleep_bug()
12 print('sleeping %d' % i)
13 if i==5:
---> 14 1/0
15 time.sleep(1)
16
ZeroDivisionError: division by zero
'''
子プロセスがエラーを起こしても、親プロセスはそのまま動き続ける。
通常の関数呼び出しでは、呼び出した関数にエラーが生じると、プログラムはそこで止まる。しかし、Poolを用いて生成した子プロセスにsleep_bug()
を実行させると、子プロセスはエラーが発生して止まるが、親プロセスではエラーは発生せずに最後まで進んでしまう。
from multiprocessing import Pool
p = Pool(1)
r = p.apply_async(sleep_bug)
p.close()
p.join() #子プロセスが終了するまで待つ。
print('Done')
# output
'''
sleeping 0
sleeping 1
sleeping 2
sleeping 3
sleeping 4
sleeping 5
Done
'''
子プロセスにエラーが生じて止まった時に、親プロセスにもエラーが伝わるようにするには、r.get()
を用いる。r.get()
は通常時には子プロセスが終了するのを待ち、子プロセスの戻り値を出力する関数であるが、子プロセスでエラーが起こった場合には、r.get()
は例外を送出し、親プロセスもそこで止まるようになる。
from multiprocessing import Pool
p = Pool(1)
r = p.apply_async(sleep_bug)
p.close()
output = r.get()
print('Done %d' % output)
# 出力
'''
sleeping 0
sleeping 1
sleeping 2
sleeping 3
sleeping 4
sleeping 5
---------------------------------------------------------------------------
RemoteTraceback Traceback (most recent call last)
RemoteTraceback:
"""
Traceback (most recent call last):
File "/opt/local/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/multiprocessing/pool.py", line 121, in worker
result = (True, func(*args, **kwds))
File "<ipython-input-41-26bb27998e63>", line 14, in sleep_bug
1/0
ZeroDivisionError: division by zero
"""
The above exception was the direct cause of the following exception:
ZeroDivisionError Traceback (most recent call last)
<ipython-input-50-fb8f5892e1a7> in <module>
3 r = p.apply_async(sleep_bug)
4 p.close()
----> 5 output = r.get()
6 print('Done %d' % output)
/opt/local/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/multiprocessing/pool.py in get(self, timeout)
655 return self._value
656 else:
--> 657 raise self._value
658
659 def _set(self, i, obj):
ZeroDivisionError: division by zero
'''
Process
を用いた場合には、エラーは表示されるが、親プロセスは最後まで進む。
from multiprocessing import Process
p = Process(target=sleep_bug)
p.start()
p.join()
print('Done')
#出力
'''
sleeping 0
sleeping 1
sleeping 2
sleeping 3
sleeping 4
sleeping 5
Process Process-35:
Traceback (most recent call last):
File "/opt/local/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/multiprocessing/process.py", line 297, in _bootstrap
self.run()
File "/opt/local/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/multiprocessing/process.py", line 99, in run
self._target(*self._args, **self._kwargs)
File "<ipython-input-41-26bb27998e63>", line 14, in sleep_bug
1/0
ZeroDivisionError: division by zero
Done
'''
親プロセスが止まっても子プロセスは動き続ける
下のスクリプトは、子プロセスが実行中2秒待ち、親プロセスはsys.exit()
でそこで自分自身を終了させるものである。以下の実行例のように、親プロセスは途中で止まっても、子プロセスは動き続ける。
from multiprocessing import Pool
p = Pool(1)
r = p.apply_async(sleep)
p.close()
r.wait(2) #子プロセスを2秒待つ
sys.exit()
#出力
'''
sleeping 0
sleeping 1
An exception has occurred, use %tb to see the full traceback.
SystemExit
sleeping 2
sleeping 3
sleeping 4
sleeping 5
sleeping 6
sleeping 7
sleeping 8
sleeping 9
'''