web-dev-qa-db-ja.com

マルチプロセッシングプールでスローされた例外は検出されません

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と同じにする方法はありますか?

64
Rob Lourens

少なくともデバッグの目的で、問題の合理的な解決策があります。現在、メインプロセスで例外を発生させるソリューションはありません。私が最初に考えたのはデコレータを使用することでしたが、ピクルできるのは モジュールのトップレベルで定義された関数 のみであるため、それはまさに正しいことです。

代わりに、単純なラッピングクラスと、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
28
Rupert Nash

何かが足りないかもしれませんが、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 を返すはずです。

50
Faheem Mitha

執筆時点で投票数が最も多いソリューションには問題があります。

_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のコメントに従って編集。

20
gozzilli

このデコレータで成功ログ例外が発生しました:

_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
_
9
Mark Foreman
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()
4
erezarnon

プロセス内の例外の完全なトレースバックを表示するモジュール 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
1
User

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

私はpdbを使ってみます:

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

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]

他のすべての回答は、すべてのタスクが実行された後にのみ失敗します。

0
Vlad