似乎当从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相同?
答案 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]
只有在执行完所有任务后,所有其他答案才会失败。