本当に必要なのは、例外を親プロセスに渡す何らかの方法ですよね? その後、必要に応じてそれらを処理できます。
を使用する場合concurrent.futures.ProcessPoolExecutor
、これは自動です。を使えばmultiprocessing.Pool
簡単です。明示的なProcess
andを使用する場合Queue
、少し作業を行う必要がありますが、それほど多くはありません。
例えば:
def run(self):
try:
for i in iter(self.inputQueue.get, 'STOP'):
# (code that does stuff)
1 / 0 # Dumb error
# (more code that does stuff)
self.outputQueue.put(result)
except Exception as e:
self.outputQueue.put(e)
次に、呼び出し元のコードは、Exception
他のものと同じように、キューから s を読み取ることができます。これの代わりに:
yield outq.pop()
これを行う:
result = outq.pop()
if isinstance(result, Exception):
raise result
yield result
(実際の親プロセスのキュー読み取りコードが何をするのかはわかりません。これは、最小限のサンプルがキューを無視するだけだからです。ただし、実際のコードが実際にはこのように機能しない場合でも、これでアイデアが説明されることを願っています。)
これは、未処理の例外がrun
. 例外を戻し、次の に進みたい場合はi in iter
、 を に移動するだけtry
ですfor
。
これも、Exception
s が有効な値ではないことを前提としています。(result, exception)
それが問題である場合、最も簡単な解決策はタプルをプッシュすることです:
def run(self):
try:
for i in iter(self.inputQueue.get, 'STOP'):
# (code that does stuff)
1 / 0 # Dumb error
# (more code that does stuff)
self.outputQueue.put((result, None))
except Exception as e:
self.outputQueue.put((None, e))
次に、ポップコードはこれを行います:
result, exception = outq.pop()
if exception:
raise exception
yield result
(err, result)
これは、すべてのコールバックに渡す node.js コールバック スタイルに似ていることに気付くかもしれません。はい、それは面倒です。そのスタイルでコードをめちゃくちゃにするつもりです。しかし、実際にはラッパー以外では使用していません。キューから値を取得したり、内部で呼び出されたりする「アプリケーションレベル」のコードはすべて、run
通常のリターン/イールドと発生した例外を確認するだけです。
ジョブをキューに入れて手動で実行している場合でもFuture
、 の仕様に合わせて を構築する (またはそのクラスをそのまま使用する) ことを検討することもできます。concurrent.futures
それほど難しいことではなく、特にデバッグ用の非常に優れた API を提供します。
最後に、ワーカーとキューを中心に構築されたほとんどのコードは、キューごとに 1 つのワーカーのみが必要であると確信している場合でも、エグゼキューター/プール設計を使用してはるかに単純化できることに注意してください。ボイラープレートをすべて破棄し、Worker.run
メソッド内のループを関数に変換します (キューに追加するのではなく、通常どおりreturn
s またはs を実行します)。raise
呼び出し側では、すべてのボイラープレートと、ジョブ関数とそのパラメーターをもう一度破棄submit
しmap
ます。
例全体を次のように縮小できます。
def job(i):
# (code that does stuff)
1 / 0 # Dumb error
# (more code that does stuff)
return result
with concurrent.futures.ProcessPoolExecutor(max_workers=1) as executor:
results = executor.map(job, range(10))
また、例外を自動的に適切に処理します。
コメントで述べたように、例外のトレースバックは子プロセスにトレースバックしません。raise result
手動呼び出し (または、プールまたはエグゼキューターを使用している場合は、プールまたはエグゼキューターの内臓)までしか進みません。
その理由は、multiprocessing.Queue
が の上に構築されておりpickle
、例外をピクルしてもトレースバックがピクルされないからです。その理由は、トレースバックをピクルできないからです。その理由は、トレースバックはローカル実行コンテキストへの参照でいっぱいであるため、別のプロセスで機能させるのは非常に難しいからです。
それで…これについて何ができますか?完全に一般的な解決策を探しに行かないでください。代わりに、実際に何が必要かを考えてください。90% の確率で、「トレースバックを使用して例外をログに記録し、続行する」または「トレースバックを使用して例外を出力し、デフォルトの未処理の例外ハンドラーstderr
と同様にする」ことが必要です。exit(1)
どちらの場合も、例外を渡す必要はまったくありません。子側でフォーマットして文字列を渡すだけです。もっと手の込んだものが必要な場合は、必要なものを正確に計算し、それを手動でまとめるのに十分な情報を渡してください。トレースバックと例外をフォーマットする方法がわからない場合は、traceback
モジュールを参照してください。とてもシンプルです。これは、ピクルスの機械に入る必要がまったくないことを意味します。(そうじゃないよ』copyreg
ピックラーを作成したり、メソッドなどを使用してホルダー クラスを作成したりし__reduce__
ますが、その必要がない場合は、そのすべてを学ぶ必要はありません)。