Python多处理进程以静默方式崩溃

时间:2013-03-20 22:51:23

标签: python python-2.7 parallel-processing multiprocessing

我正在使用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)

3 个答案:

答案 0 :(得分:13)

你真正想要的是将异常传递给父进程的一些方法,对吧?然后你可以随意处理它们。

如果您使用concurrent.futures.ProcessPoolExecutor,则这是自动的。如果你使用multiprocessing.Pool,这是微不足道的。如果您使用明确的ProcessQueue,则必须做一些工作,但

例如:

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内调用只是看到正常的返回/收益和引发异常。

您甚至可以考虑构建Futureconcurrent.futures的规范(或按原样使用该类),即使您正在排队并手动执行。它并不难,它为您提供了一个非常好的API,特别是用于调试。

最后,值得注意的是,使用执行程序/池设计可以使围绕工作人员和队列构建的大多数代码变得更加简单,即使您完全确定每个队列只需要一个工作程序。只需废弃所有样板,然后将Worker.run方法中的循环转换为函数(正常returnraise s,而不是附加到队列中。在主叫方面,再次废弃所有样板,并使用其参数submitmap作业函数。

您的整个示例可以简化为:

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%的情况下,您想要的是“记录异常,使用回溯,并继续”或“使用回溯打印异常,将stderrexit(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

我很惊讶(如果)你什么都没得到。