未检测到在多处理池中引发的异常


73

看来,当从multiprocessing.Pool进程引发异常时,没有堆栈跟踪或任何其他指示其已失败的迹象。例:

from multiprocessing import Pool 

def go():
    print(1)
    raise Exception()
    print(2)

p = Pool()
p.apply_async(go)
p.close()
p.join()

打印1并静默停止。有趣的是,引发BaseException可以工作。有什么办法可以使所有异常的行为与BaseException相同?


1
我有同样的问题。原因如下:工作进程捕获到Exception并将失败代码和该异常放在结果队列中。回到主流程中,池的结果处理程序线程获取失败代码,而忽略它。某种猴子补丁调试模式是可能的。一种替代方法是确保您的工作程序函数捕获任何异常,将其返回,并提供一个错误代码供您的处理程序打印。
鲁珀特·纳什

这已在此处得到解答:stackoverflow.com/a/26096355/512111
j08lue

Answers:


30

我有一个合理的解决方案,至少用于调试目的。我目前没有一个可以在主要流程中引发异常的解决方案。我的第一个想法是使用装饰器,但是您只能腌制在模块顶层定义的函数,所以没错。

取而代之的是一个简单的包装类和一个Pool子类,将其用于apply_async(因此apply)。我将map_async作为练习留给读者。

import traceback
from multiprocessing.pool import Pool
import multiprocessing

# Shortcut to multiprocessing's logger
def error(msg, *args):
    return multiprocessing.get_logger().error(msg, *args)

class LogExceptions(object):
    def __init__(self, callable):
        self.__callable = callable

    def __call__(self, *args, **kwargs):
        try:
            result = self.__callable(*args, **kwargs)

        except Exception as e:
            # Here we add some debugging help. If multiprocessing's
            # debugging is on, it will arrange to log the traceback
            error(traceback.format_exc())
            # Re-raise the original exception so the Pool worker can
            # clean up
            raise

        # It was fine, give a normal answer
        return result

class LoggingPool(Pool):
    def apply_async(self, func, args=(), kwds={}, callback=None):
        return Pool.apply_async(self, LogExceptions(func), args, kwds, callback)

def go():
    print(1)
    raise Exception()
    print(2)

multiprocessing.log_to_stderr()
p = LoggingPool(processes=1)

p.apply_async(go)
p.close()
p.join()

这给了我:

1
[ERROR/PoolWorker-1] Traceback (most recent call last):
  File "mpdebug.py", line 24, in __call__
    result = self.__callable(*args, **kwargs)
  File "mpdebug.py", line 44, in go
    raise Exception()
Exception

太糟糕了,没有更简单的解决方案(或者我的错误),但这可以完成工作,谢谢!
罗布·劳伦斯

5
我已经意识到,可以使用@functools.wraps(func)装饰器来装饰包装纸。这使修饰后的函数看起来像在模块顶层定义的函数。
鲁珀特·纳什

1
此答案中的解决方案更简单,支持在主要过程中重新引发错误!
j08lue

1
@ j08lue-答案不错,但有3个缺点:1)额外的依赖2)必须用try / except包装您的worker函数,并且返回包装对象的逻辑3)必须嗅探返回类型并重新引发。从好的方面来说,我同意在您的主线程中进行实际的追溯更好。
Rupert Nash

1
@RupertNash实际上我的意思是更像这个新答案中的用法。这解决了缺点
3。– j08lue

56

也许我缺少了一些东西,但是那不是getResult对象的方法返回的东西吗?请参阅过程池

类multiprocessing.pool.AsyncResult

Pool.apply_async()和Pool.map_async()。get([timeout])
返回的结果的类在到达时返回结果。如果超时不为None并且结果在超时秒内没有到达,则引发multiprocessing.TimeoutError。如果远程调用引发了异常,则该异常将由get()引发。

因此,稍微修改一下示例,就可以做到

from multiprocessing import Pool

def go():
    print(1)
    raise Exception("foobar")
    print(2)

p = Pool()
x = p.apply_async(go)
x.get()
p.close()
p.join()

结果给出

1
Traceback (most recent call last):
  File "rob.py", line 10, in <module>
    x.get()
  File "/usr/lib/python2.6/multiprocessing/pool.py", line 422, in get
    raise self._value
Exception: foobar

这并不完全令人满意,因为它不打印回溯,但是总比没有好。

更新:Richard Oudkerk提供了此错误已在Python 3.4中修复。请参阅问题multiprocess.pool的get方法。异步应返回完整的traceback


让我知道您是否知道为什么它不返回回溯。由于它能够返回错误值,因此它也应该能够返回回溯。我可能会在一些合适的论坛上问-也许是一些Python开发列表。顺便说一句,您可能已经猜到了,我在尝试找出同一件事时遇到了您的问题。:-)
Faheem Mitha 2012年

4
注意:要对一堆同时运行的任务执行此操作,您应该将所有结果保存在列表中,然后使用get()遍历每个结果,如果不想对代码进行处理,则可能被try / catch包围第一个错误。
dfrankow

@dfrankow这是一个很好的建议。您是否愿意在新答案中提出可能的实施方案?我敢打赌,这将非常有用。;)
JoErNanO 2014年

可悲的是,一年多之后,我完全忘记了所有这些。
dfrankow 2014年

2
答案中的代码将在上等待x.get(),这破坏了异步应用任务的意义。@dfrankow发表的关于将结果保存到列表中,然后get在最后进行提示的评论是一个更好的解决方案。
gozzilli 2015年

21

在撰写本文时获得最多投票的解决方案存在问题:

from multiprocessing import Pool

def go():
    print(1)
    raise Exception("foobar")
    print(2)

p = Pool()
x = p.apply_async(go)
x.get()  ## waiting here for go() to complete...
p.close()
p.join()

正如@dfrankow指出的那样,它将等待x.get(),从而破坏了异步运行任务的意义。因此,为了提高效率(特别是如果您的工作人员功能go花费很长时间),我将其更改为:

from multiprocessing import Pool

def go(x):
    print(1)
    # task_that_takes_a_long_time()
    raise Exception("Can't go anywhere.")
    print(2)
    return x**2

p = Pool()
results = []
for x in range(1000):
    results.append( p.apply_async(go, [x]) )

p.close()

for r in results:
     r.get()

优点:worker函数是异步运行的,因此,例如,如果您在多个内核上运行许多任务,它将比原始解决方案高效得多。

缺点:如果worker函数中有异常,则只有在池完成所有任务才会引发该异常。这可能是或可能不是理想的行为。根据@colinfang的评论进行编辑,从而解决了此问题。


好功夫。但是,由于您的示例是基于存在多个结果的假设而定的,因此也许可以对其进行扩展以使实际上存在多个结果?另外,您将写:“特别是如果您具有辅助功能”。那应该是你的。
Faheem Mitha 2015年

您说得对,谢谢。我对示例进行了扩展。
gozzilli

1
凉。另外,您可能要尝试/例外,具体取决于您要如何容忍提取中的错误。
dfrankow

@gozzilli您可以for r in ... r.get()p.close()和之间插入一个p.join(),以便在遇到例外时立即退出
colinfang 2015年

@colinfang我相信那是return null因为尚未进行计算-除非您,否则它不会等待它join()
gozzilli

9

我已经使用此装饰器成功记录了异常:

import traceback, functools, multiprocessing

def trace_unhandled_exceptions(func):
    @functools.wraps(func)
    def wrapped_func(*args, **kwargs):
        try:
            func(*args, **kwargs)
        except:
            print 'Exception in '+func.__name__
            traceback.print_exc()
    return wrapped_func

加上问题中的代码,

@trace_unhandled_exceptions
def go():
    print(1)
    raise Exception()
    print(2)

p = multiprocessing.Pool(1)

p.apply_async(go)
p.close()
p.join()

只需装饰传递给进程池的函数即可。这项工作的关键是@functools.wraps(func)否则多处理将抛出PicklingError

上面的代码给出

1
Exception in go
Traceback (most recent call last):
  File "<stdin>", line 5, in wrapped_func
  File "<stdin>", line 4, in go
Exception

如果并行运行的函数(在这种情况下为go())返回值,则此方法不起作用。装饰器不会传递返回值。除此之外,我喜欢这种解决方案。
MD004

要传递返回值,只需像这样修改wrapper_func:def wrap_func(* args,** kwargs):result = None尝试:result = func(* args,** kwargs)除外:print('+ func中的'Exception。 __name__)traceback.print_exc()返回结果`就像魅力;)
MoTSCHIGGE '16

5
import logging
from multiprocessing import Pool

def proc_wrapper(func, *args, **kwargs):
    """Print exception because multiprocessing lib doesn't return them right."""
    try:
        return func(*args, **kwargs)
    except Exception as e:
        logging.exception(e)
        raise

def go(x):
    print x
    raise Exception("foobar")

p = Pool()
p.apply_async(proc_wrapper, (go, 5))
p.join()
p.close()

3

既然您已经使用过apply_sync,我想用例就是要执行一些同步任务。使用回调进行处理是另一种选择。请注意,此选项仅适用于python3.2及更高版本,而不适用于python2.7。

from multiprocessing import Pool

def callback(result):
    print('success', result)

def callback_error(result):
    print('error', result)

def go():
    print(1)
    raise Exception()
    print(2)

p = Pool()
p.apply_async(go, callback=callback, error_callback=callback_error)

# You can do another things

p.close()
p.join()

有没有这样error_callbakapply_async方法,请参阅docs.python.org/3.1/library/...
三居

1
对于更高版本:docs.python.org/3/library/…–
Asoul

3

由于已经有了不错的答案,因此multiprocessing.Pool,我将使用另一种方法来提供完整的解决方案。

对于python >= 3.2以下解决方案似乎是最简单的:

from concurrent.futures import ProcessPoolExecutor, wait

def go():
    print(1)
    raise Exception()
    print(2)


futures = []
with ProcessPoolExecutor() as p:
    for i in range(10):
        futures.append(p.submit(go))

results = [f.result() for f in futures]

优点:

  • 很少的代码
  • 在主过程中引发异常
  • 提供堆栈跟踪
  • 没有外部依赖

有关API的更多信息,请查看

此外,如果您要提交大量任务,并且您希望主流程在其中一项任务失败后立即失败,则可以使用以下代码段:

from concurrent.futures import ProcessPoolExecutor, wait, FIRST_EXCEPTION, as_completed
import time


def go():
    print(1)
    time.sleep(0.3)
    raise Exception()
    print(2)


futures = []
with ProcessPoolExecutor(1) as p:
    for i in range(10):
        futures.append(p.submit(go))

    for f in as_completed(futures):
        if f.exception() is not None:
            for f in futures:
                f.cancel()
            break

[f.result() for f in futures]

所有其他任务只有在执行完所有任务后才会失败。


1

我创建了一个模块RemoteException.py,该模块显示了流程中异常的完整回溯。Python2。下载并将其添加到您的代码中:

import RemoteException

@RemoteException.showError
def go():
    raise Exception('Error!')

if __name__ == '__main__':
    import multiprocessing
    p = multiprocessing.Pool(processes = 1)
    r = p.apply(go) # full traceback is shown here

0

我会尝试使用pdb:

import pdb
import sys
def handler(type, value, tb):
  pdb.pm()
sys.excepthook = handler

在那种情况下它从未到达处理程序,很奇怪
Rob Lourens
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.