Help us understand the problem. What is going on with this article?

[python, multiprocessing] multiprocessing使用時の例外に対する挙動

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
'''
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした