未检测到多处理池中抛出的异常

时间:2011-07-18 02:46:12

标签: python exception multiprocessing

似乎当从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相同?

9 个答案:

答案 0 :(得分:48)

也许我错过了什么,但这不是Result对象的get方法返回的内容吗?请参阅Process Pools

  

class 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

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

更新:这个错误已在Python 3.4中修复,由Richard Oudkerk提供。请参阅问题get method of multiprocessing.pool.Async should return full traceback

答案 1 :(得分:25)

我有一个合理的解决方案,至少在调试方面。我目前没有一个解决方案可以在主流程中引发异常。我的第一个想法是使用一个装饰器,但你只能腌制functions defined at the top level of a module,所以就这样了。

相反,一个简单的包装类和一个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

答案 2 :(得分:18)

撰写本文时投票率最高的解决方案存在问题:

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函数中存在异常,则只有在池完成所有任务后才会引发。这可能是也可能不是理想的行为。根据@ colinfang的评论编辑,修正了这个问题。

答案 3 :(得分: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

答案 4 :(得分:2)

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()

答案 5 :(得分:1)

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

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

答案 6 :(得分:0)

我尝试使用pdb:

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

答案 7 :(得分:0)

由于您使用过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()

答案 8 :(得分:0)

由于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的更多信息,请查看:https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ProcessPoolExecutor

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

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]

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