我正在使用Python 2.7.3。我使用子类multiprocessing.Process
对象并行化了一些代码。如果我的子类Process对象中的代码没有错误,那么一切运行正常。但是如果我的子类Process对象中的代码有错误,它们显然会无声地崩溃(没有堆栈跟踪打印到父shell),CPU使用率将降至零。父代码永远不会崩溃,给人的印象是执行只是挂起。同时,很难追踪代码中的错误,因为没有指出错误的位置。
我在stackoverflow上找不到任何其他问题来处理同样的问题。
我认为子类化的Process对象似乎是静默崩溃的,因为它们无法向父shell发送错误消息,但我想知道我能做些什么,这样我至少可以更高效地调试(和所以我的代码的其他用户也可以在遇到问题时告诉我。)
编辑:我的实际代码太复杂了,但是带有错误的子类Process对象的一个简单例子就是这样的:
from multiprocessing import Process, Queue
class Worker(Process):
def __init__(self, inputQueue, outputQueue):
super(Worker, self).__init__()
self.inputQueue = inputQueue
self.outputQueue = outputQueue
def run(self):
for i in iter(self.inputQueue.get, 'STOP'):
# (code that does stuff)
1 / 0 # Dumb error
# (more code that does stuff)
self.outputQueue.put(result)
答案 0 :(得分:13)
你真正想要的是将异常传递给父进程的一些方法,对吧?然后你可以随意处理它们。
如果您使用concurrent.futures.ProcessPoolExecutor
,则这是自动的。如果你使用multiprocessing.Pool
,这是微不足道的。如果您使用明确的Process
和Queue
,则必须做一些工作,但 。
例如:
def run(self):
try:
for i in iter(self.inputQueue.get, 'STOP'):
# (code that does stuff)
1 / 0 # Dumb error
# (more code that does stuff)
self.outputQueue.put(result)
except Exception as e:
self.outputQueue.put(e)
然后,您的调用代码可以像其他任何内容一样从队列中读取Exception
。而不是:
yield outq.pop()
这样做:
result = outq.pop()
if isinstance(result, Exception):
raise result
yield result
(我不知道你的实际父进程队列读取代码是做什么的,因为你的最小样本只是忽略了队列。但希望这解释了这个想法,即使你的真实代码实际上并没有像这样工作。 )
这假定您要中止任何未处理的异常,使其达到run
。如果您想要传回异常并继续下一个i in iter
,只需将try
移到for
,而不是在其周围。
这也假定Exception
s不是有效值。如果这是一个问题,最简单的解决方案就是推送(result, exception)
元组:
def run(self):
try:
for i in iter(self.inputQueue.get, 'STOP'):
# (code that does stuff)
1 / 0 # Dumb error
# (more code that does stuff)
self.outputQueue.put((result, None))
except Exception as e:
self.outputQueue.put((None, e))
然后,您的弹出代码执行此操作:
result, exception = outq.pop()
if exception:
raise exception
yield result
您可能会注意到这与node.js回调样式类似,您可以将(err, result)
传递给每个回调。是的,这很烦人,而且你会以这种方式弄乱代码。但除了包装器外,你实际上并没有使用它;所有“应用程序级”代码从队列中获取值或在run
内调用只是看到正常的返回/收益和引发异常。
您甚至可以考虑构建Future
到concurrent.futures
的规范(或按原样使用该类),即使您正在排队并手动执行。它并不难,它为您提供了一个非常好的API,特别是用于调试。
最后,值得注意的是,使用执行程序/池设计可以使围绕工作人员和队列构建的大多数代码变得更加简单,即使您完全确定每个队列只需要一个工作程序。只需废弃所有样板,然后将Worker.run
方法中的循环转换为函数(正常return
或raise
s,而不是附加到队列中。在主叫方面,再次废弃所有样板,并使用其参数submit
或map
作业函数。
您的整个示例可以简化为:
def job(i):
# (code that does stuff)
1 / 0 # Dumb error
# (more code that does stuff)
return result
with concurrent.futures.ProcessPoolExecutor(max_workers=1) as executor:
results = executor.map(job, range(10))
它会自动正确处理异常。
正如您在评论中提到的,异常的回溯不会追溯到子进程;它只能进行手动raise result
调用(或者,如果您使用的是池或执行程序,则是池或执行程序的内容)。
原因是multiprocessing.Queue
建立在pickle
之上,而酸洗异常并没有扼杀他们的追溯。原因是你不能腌制回溯。原因是回溯充满了对本地执行上下文的引用,因此让它们在另一个进程中工作将非常困难。
那么......你能做些什么呢?不要去寻找一个完全通用的解决方案。相反,想想你真正需要什么。 90%的情况下,您想要的是“记录异常,使用回溯,并继续”或“使用回溯打印异常,将stderr
和exit(1)
打印为默认的未处理异常处理程序” 。对于其中任何一个,您根本不需要传递异常;只需在子端进行格式化并传递一个字符串。如果您做需要更多花哨的东西,请确切地计算出您需要的东西,并传递足够的信息以手动将它们组合在一起。如果您不知道如何格式化回溯和异常,请参阅traceback
模块。这很简单。这意味着你根本不需要进入泡菜机械。 (并不是说copyreg
挑选者或用__reduce__
方法或任何东西写一个持有人类很难,但如果你不需要,为什么要学习这一切?)
答案 1 :(得分:2)
我建议使用此方法来显示流程的异常
from multiprocessing import Queue, Process, RawValue, Semaphore, Lock, Pool
import traceback
run_old = Process.run
def run_new(*args, **kwargs):
try:
run_old(*args, **kwargs)
except (KeyboardInterrupt, SystemExit):
raise
except:
traceback.print_exc(file=sys.stdout)
Process.run = run_new
答案 2 :(得分:1)
这不是一个答案,只是一个扩展的评论。请运行此程序,告诉我们您得到的输出(如果有):
from multiprocessing import Process, Queue
class Worker(Process):
def __init__(self, inputQueue, outputQueue):
super(Worker, self).__init__()
self.inputQueue = inputQueue
self.outputQueue = outputQueue
def run(self):
for i in iter(self.inputQueue.get, 'STOP'):
# (code that does stuff)
1 / 0 # Dumb error
# (more code that does stuff)
self.outputQueue.put(result)
if __name__ == '__main__':
inq, outq = Queue(), Queue()
inq.put(1)
inq.put('STOP')
w = Worker(inq, outq)
w.start()
我明白了:
% test.py
Process Worker-1:
Traceback (most recent call last):
File "/usr/lib/python2.7/multiprocessing/process.py", line 258, in _bootstrap
self.run()
File "/home/unutbu/pybin/test.py", line 21, in run
1 / 0 # Dumb error
ZeroDivisionError: integer division or modulo by zero
我很惊讶(如果)你什么都没得到。