我的理解是,如果输入了try,最后的子句必须 *总是*。
import random
from multiprocessing import Pool
from time import sleep
def Process(x):
try:
print x
sleep(random.random())
raise Exception('Exception: ' + x)
finally:
print 'Finally: ' + x
Pool(3).map(Process, ['1','2','3'])
预期输出是对于每个由第8行单独打印的x,必须出现'Finally x'。
示例输出:
$ python bug.py
1
2
3
Finally: 2
Traceback (most recent call last):
File "bug.py", line 14, in <module>
Pool(3).map(Process, ['1','2','3'])
File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/multiprocessing/pool.py", line 225, in map
return self.map_async(func, iterable, chunksize).get()
File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/multiprocessing/pool.py", line 522, in get
raise self._value
Exception: Exception: 2
似乎终止一个进程的异常终止了父进程和兄弟进程,即使在其他进程中还需要进一步完成 工作。
为什么我错了?为什么这是正确的?如果这是正确的,那么应该如何在多进程Python中安全地清理资源?
答案 0 :(得分:31)
简短回答:SIGTERM
胜过finally
。
答案很长:使用mp.log_to_stderr()
打开日志记录:
import random
import multiprocessing as mp
import time
import logging
logger=mp.log_to_stderr(logging.DEBUG)
def Process(x):
try:
logger.info(x)
time.sleep(random.random())
raise Exception('Exception: ' + x)
finally:
logger.info('Finally: ' + x)
result=mp.Pool(3).map(Process, ['1','2','3'])
日志记录输出包括:
[DEBUG/MainProcess] terminating workers
这对应于multiprocessing.pool._terminate_pool
中的此代码:
if pool and hasattr(pool[0], 'terminate'):
debug('terminating workers')
for p in pool:
p.terminate()
p
中的每个pool
都是multiprocessing.Process
,并且调用terminate
(至少在非Windows机器上)调用SIGTERM:
来自multiprocessing/forking.py
:
class Popen(object)
def terminate(self):
...
try:
os.kill(self.pid, signal.SIGTERM)
except OSError, e:
if self.wait(timeout=0.1) is None:
raise
因此,当try
套件中的Python流程发送SIGTERM
时会发生什么。
考虑以下示例(test.py):
import time
def worker():
try:
time.sleep(100)
finally:
print('enter finally')
time.sleep(2)
print('exit finally')
worker()
如果你运行它,然后发送一个SIGTERM
,那么过程立即结束,而不进入finally
套件,没有输出证明,也没有延迟。
在一个终端:
% test.py
在第二个终端:
% pkill -TERM -f "test.py"
第一个终端的结果:
Terminated
将此问题与发送流程时发生的情况进行比较SIGINT
(C-c
):
在第二个终端:
% pkill -INT -f "test.py"
第一个终端的结果:
enter finally
exit finally
Traceback (most recent call last):
File "/home/unutbu/pybin/test.py", line 14, in <module>
worker()
File "/home/unutbu/pybin/test.py", line 8, in worker
time.sleep(100)
KeyboardInterrupt
结论:SIGTERM
胜过finally
。
答案 1 :(得分:4)
来自answer的unutbu明确解释为什么您获得了观察到的行为。但是,应该强调SIGTERM的发送只是因为multiprocessing.pool._terminate_pool
的实现方式。如果您可以避免使用Pool
,那么您可以获得所需的行为。这是一个borrowed example:
from multiprocessing import Process
from time import sleep
import random
def f(x):
try:
sleep(random.random()*10)
raise Exception
except:
print "Caught exception in process:", x
# Make this last longer than the except clause in main.
sleep(3)
finally:
print "Cleaning up process:", x
if __name__ == '__main__':
processes = []
for i in range(4):
p = Process(target=f, args=(i,))
p.start()
processes.append(p)
try:
for process in processes:
process.join()
except:
print "Caught exception in main."
finally:
print "Cleaning up main."
发送SIGINT后,示例输出为:
Caught exception in process: 0
^C
Cleaning up process: 0
Caught exception in main.
Cleaning up main.
Caught exception in process: 1
Caught exception in process: 2
Caught exception in process: 3
Cleaning up process: 1
Cleaning up process: 2
Cleaning up process: 3
请注意,所有进程都会运行finally
子句。如果您需要共享内存,请考虑使用Queue
,Pipe
,Manager
或某些外部商店,例如redis
或sqlite3
。
答案 2 :(得分:1)
finally
重新引发原始异常unless you return
from it。然后由Pool.map
引发异常并终止整个应用程序。子进程终止,您没有看到其他异常。
您可以添加return
以吞下例外:
def Process(x):
try:
print x
sleep(random.random())
raise Exception('Exception: ' + x)
finally:
print 'Finally: ' + x
return
然后,当发生异常时,您应该在None
结果中map
。