Python多处理进程以静默方式崩溃
我正在使用Python 2.7.3。我使用子类Python多处理进程以静默方式崩溃,python,python-2.7,parallel-processing,multiprocessing,Python,Python 2.7,Parallel Processing,Multiprocessing,我正在使用Python 2.7.3。我使用子类multiprocessing.Process对象对一些代码进行了并行化。如果我的子类流程对象中的代码中没有错误,那么一切都可以正常运行。但是,如果我的子类进程对象中的代码中有错误,它们显然会无声地崩溃(没有stacktrace打印到父shell),CPU使用率将降至零。父代码从不崩溃,给人的印象是执行只是挂起。同时,很难找出代码中的错误在哪里,因为没有给出错误在哪里的指示 我在stackoverflow上找不到任何其他关于同一问题的问题 我猜子类化
multiprocessing.Process
对象对一些代码进行了并行化。如果我的子类流程对象中的代码中没有错误,那么一切都可以正常运行。但是,如果我的子类进程对象中的代码中有错误,它们显然会无声地崩溃(没有stacktrace打印到父shell),CPU使用率将降至零。父代码从不崩溃,给人的印象是执行只是挂起。同时,很难找出代码中的错误在哪里,因为没有给出错误在哪里的指示
我在stackoverflow上找不到任何其他关于同一问题的问题
我猜子类化的流程对象似乎会悄无声息地崩溃,因为它们无法将错误消息打印到父级的shell中,但我想知道我能做些什么,这样我至少可以更高效地进行调试(这样我的代码的其他用户也可以在遇到问题时告诉我)
EDIT:我的实际代码太复杂了,但是一个小例子是子类流程对象中有一个错误,它是这样的:
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)
这不是一个答案,只是一个延伸的评论。请运行此程序并告诉我们您得到了什么输出(如果有): 我得到:
% 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
我很惊讶(如果)您什么都没有得到。您真正想要的是某种将异常传递给父进程的方法,对吗?然后你可以随心所欲地处理它们 如果使用,这是自动的。如果你使用,它是微不足道的。如果您使用显式的
过程
和队列
,您需要做一些工作,但并没有那么多
例如:
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
s。与此相反:
yield outq.pop()
这样做:
result = outq.pop()
if isinstance(result, Exception):
raise result
yield result
(我不知道您的实际父进程队列读取代码是做什么的,因为您的最小样本只是忽略了队列。但希望这能解释这个想法,即使您的实际代码实际上不是这样工作的。)
这假设您希望中止任何未经处理的异常,该异常将导致运行。如果要传回异常并继续执行iter中的下一个i
,只需将try
移动到for
中,而不是绕过它
这还假设异常
s不是有效值。如果这是一个问题,最简单的解决方案就是推送(结果,异常)
元组:
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
内部调用的“应用程序级”代码只会看到正常的回报/收益和引发的异常
您可能甚至想考虑将代码< >将来/代码>到代码< >并发.Noest< /Cal> >(或使用该类AS),即使您正在执行作业队列并手动执行。这并不难,它为您提供了一个非常好的API,特别是用于调试的API
最后,值得注意的是,通过执行器/池设计,大多数围绕工作者和队列构建的代码可以变得简单得多,即使您绝对确定每个队列只需要一个工作者。只需废弃所有样板文件,并将Worker中的循环转换为一个函数。将方法运行为一个函数(它只正常返回s或提升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))
它会自动正确处理异常
正如您在评论中提到的,异常的回溯不会追溯到子进程;它只适用于手动raiseresult
调用(或者,如果您使用的是池或执行器,则是池或执行器的内脏)
原因是,multiprocessing.Queue
构建在pickle
之上,pickle异常不会pickle它们的回溯。原因是你不能对追踪进行酸洗。原因是回溯中充满了对本地执行上下文的引用,所以要让它们在另一个进程中工作是非常困难的
那么…你能做些什么?不要去寻找一个完全通用的解决方案。相反,想想你真正需要什么。90%的情况下,您需要的是“记录异常,并进行回溯,然后继续”或“将异常打印到stderr
和exit(1)
,就像默认的未处理异常处理程序一样”。对于这两种情况,您根本不需要传递异常;只需在子端格式化它并传递一个字符串即可。如果你确实需要一些更奇特的东西,准确地计算出你需要的东西,并传递足够的信息来手动组合起来。如果您不知道如何格式化回溯和异常,请参阅模块。这很简单。这意味着你根本不需要进入腌菜机。(并不是说用\uuuu reduce\uuuuu
方法编写一个pickler或holder类很难,但如果不需要,为什么要学习所有这些呢?我建议这样的方法来显示流程异常
from multiprocessing import Process
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
你能发布一个简单的测试用例来说明这个问题吗?@Blender-yes。添加了一些代码。这是Python多处理的一个常见问题。我建议用雷。像这样的异常可以很好地进行开箱即用的传播。如果他在shell中没有得到POSIX的任何信息,我会感到惊讶。但是在Windows上,或者在IDLE或PyDev中,或者如果父进程是GUI
from multiprocessing import Process
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