Python多进程:子进程崩溃?
当一个Python脚本打开了子进程,而其中一个进程崩溃了,会发生什么呢?
https://stackoverflow.com/a/18216437/311901
主进程会崩溃吗?
其他的子进程会崩溃吗?
有没有信号或者其他事件会被传递出去?
1 个回答
当你使用 multiprocessing.Pool
时,如果池中的某个子进程崩溃了,你不会收到任何通知,系统会立即启动一个新进程来替代它:
>>> import multiprocessing
>>> p = multiprocessing.Pool()
>>> p._processes
4
>>> p._pool
[<Process(PoolWorker-1, started daemon)>, <Process(PoolWorker-2, started daemon)>, <Process(PoolWorker-3, started daemon)>, <Process(PoolWorker-4, started daemon)>]
>>> [proc.pid for proc in p._pool]
[30760, 30761, 30762, 30763]
然后在另一个窗口中:
dan@dantop:~$ kill 30763
回到池子里:
>>> [proc.pid for proc in p._pool]
[30760, 30761, 30762, 30767] # New pid for the last process
你可以继续使用这个池,就好像什么事都没发生一样。不过,那个崩溃的子进程当时正在处理的工作项将不会被完成或重新启动。如果你正在运行一个阻塞的 map
或 apply
调用,而这个调用依赖于那个工作项完成,那么它可能会一直挂着。虽然有一个bug报告,但这个问题只在concurrent.futures.ProcessPoolExecutor
中得到了解决,而不是在 multiprocessing.Pool
中。从Python 3.3开始,如果一个子进程崩溃,ProcessPoolExecutor
会抛出一个 BrokenProcessPool
异常,并不允许再使用这个池。可惜的是,multiprocessing
没有得到这个改进。目前,如果你想防止池调用因为子进程崩溃而永远阻塞,你需要使用一些不太优雅的解决方法。
注意:上面的情况只适用于池中的进程真正崩溃,也就是说这个进程完全死掉了。如果一个子进程抛出异常,当你尝试获取工作项的结果时,这个异常会被传递到父进程:
>>> def f(): raise Exception("Oh no")
...
>>> pool = multiprocessing.Pool()
>>> result = pool.apply_async(f)
>>> result.get()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.7/multiprocessing/pool.py", line 528, in get
raise self._value
Exception: Oh no
当你直接使用 multiprocessing.Process
时,如果进程崩溃,进程对象会显示这个进程以非零的退出代码退出:
>>> def f(): time.sleep(30)
...
>>> p = multiprocessing.Process(target=f)
>>> p.start()
>>> p.join() # Kill the process while this is blocking, and join immediately ends
>>> p.exitcode
-15
如果抛出异常,行为也是类似的:
from multiprocessing import Process
def f(x):
raise Exception("Oh no")
if __name__ == '__main__':
p = Process(target=f)
p.start()
p.join()
print(p.exitcode)
print("done")
输出:
Process Process-1:
Traceback (most recent call last):
File "/usr/lib/python3.2/multiprocessing/process.py", line 267, in _bootstrap
self.run()
File "/usr/lib/python3.2/multiprocessing/process.py", line 116, in run
self._target(*self._args, **self._kwargs)
TypeError: f() takes exactly 1 argument (0 given)
1
done
如你所见,子进程的错误追踪信息被打印出来,但这并不影响主进程的执行,主进程能够显示子进程的 exitcode
是 1
。