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と同じにする方法はありますか?
少なくともデバッグの目的で、問題の合理的な解決策があります。現在、メインプロセスで例外を発生させるソリューションはありません。私が最初に考えたのはデコレータを使用することでしたが、ピクルできるのは モジュールのトップレベルで定義された関数 のみであるため、それはまさに正しいことです。
代わりに、単純なラッピングクラスと、apply_async
(したがってapply
)にこれを使用するPoolサブクラス。読者のための演習として、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
何かが足りないかもしれませんが、Resultオブジェクトのget
メソッドが返すものではありませんか? Process Pools を参照してください。
クラス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の好意により修正されました。multiprocessing.pool.Asyncの getメソッドが完全なtraceback を返すはずです。
執筆時点で投票数が最も多いソリューションには問題があります。
_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()
_
利点:ワーカー関数は非同期で実行されるため、たとえば複数のコアで多くのタスクを実行している場合、元のソリューションよりもはるかに効率的です。
短所:ワーカー関数に例外がある場合、例外が発生しますafterプールはすべてのタスクを完了しました。これは望ましい動作である場合とそうでない場合があります。 これを修正した@colinfangのコメントに従って編集。
このデコレータで成功ログ例外が発生しました:
_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
_
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()
プロセス内の例外の完全なトレースバックを表示するモジュール RemoteException.py を作成しました。 Python2。 ダウンロードしてください そしてこれをコードに追加します:
import RemoteException
@RemoteException.showError
def go():
raise Exception('Error!')
if __== '__main__':
import multiprocessing
p = multiprocessing.Pool(processes = 1)
r = p.apply(go) # full traceback is shown here
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()
私はpdbを使ってみます:
import pdb
import sys
def handler(type, value, tb):
pdb.pm()
sys.excepthook = handler
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
さらに、多数のタスクを送信していて、タスクの1つが失敗するとすぐにメインプロセスを失敗させたい場合は、次のスニペットを使用できます。
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]
他のすべての回答は、すべてのタスクが実行された後にのみ失敗します。