web-dev-qa-db-ja.com

Python:multiprocessing.map:1つのプロセスで例外が発生した場合、他のプロセスのブロックが最終的に呼び出されないのはなぜですか?

私の理解では、tryが入力されている場合、finally節必須 *常に*実行されます。

import random

from multiprocessing import Pool
from time import sleep

def Process(x):
  try:
    print x
    sleep(random.random())
    raise Exception('Exception: ' + x)
  finally:
    print 'Finally: ' + x

Pool(3).map(Process, ['1','2','3'])

期待される出力は、8行目で単独で出力されるxのそれぞれについて、必須「最後にx」が出現することです。

出力例:

$ python bug.py 
1
2
3
Finally: 2
Traceback (most recent call last):
  File "bug.py", line 14, in <module>
    Pool(3).map(Process, ['1','2','3'])
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/multiprocessing/pool.py", line 225, in map
    return self.map_async(func, iterable, chunksize).get()
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/multiprocessing/pool.py", line 522, in get
    raise self._value
Exception: Exception: 2

1つのプロセスを終了する例外は、他のプロセスで実行する必要のある作業がさらにある場合でも、親プロセスと兄弟プロセスを終了するようです必須

なぜ私は間違っているのですか?なぜこれが正しいのですか?これが正しければ、どのようにshouldマルチプロセスPythonでリソースを安全にクリーンアップしますか?

26

短い答え:SIGTERM trumps finally

長い答え:mp.log_to_stderr()でロギングをオンにする:

import random
import multiprocessing as mp
import time
import logging

logger=mp.log_to_stderr(logging.DEBUG)

def Process(x):
    try:
        logger.info(x)
        time.sleep(random.random())
        raise Exception('Exception: ' + x)
    finally:
        logger.info('Finally: ' + x)

result=mp.Pool(3).map(Process, ['1','2','3'])

ロギング出力には次のものが含まれます。

[DEBUG/MainProcess] terminating workers

これはmultiprocessing.pool._terminate_poolのこのコードに対応します。

    if pool and hasattr(pool[0], 'terminate'):
        debug('terminating workers')
        for p in pool:
            p.terminate()

pの各poolmultiprocessing.Processであり、terminateを呼び出すと(少なくともWindows以外のマシンでは)SIGTERMが呼び出されます。

multiprocessing/forking.pyから:

class Popen(object)
    def terminate(self):
        ...
            try:
                os.kill(self.pid, signal.SIGTERM)
            except OSError, e:
                if self.wait(timeout=0.1) is None:
                    raise

つまり、Python tryスイートのプロセスがSIGTERMに送信されたときに何が起こるか

次の例(test.py)について考えてみます。

import time    
def worker():
    try:
        time.sleep(100)        
    finally:
        print('enter finally')
        time.sleep(2) 
        print('exit finally')    
worker()

実行してからSIGTERMを送信すると、出力や遅延がないことからわかるように、プロセスはfinallyスイートに入らずにすぐに終了します。

1つの端末で:

% test.py

2番目のターミナル:

% pkill -TERM -f "test.py"

最初の端末になります:

Terminated

プロセスがSIGINTC-c)に送信されたときに何が起こるかと比較してください。

2番目のターミナル:

% pkill -INT -f "test.py"

最初の端末になります:

enter finally
exit finally
Traceback (most recent call last):
  File "/home/unutbu/pybin/test.py", line 14, in <module>
    worker()
  File "/home/unutbu/pybin/test.py", line 8, in worker
    time.sleep(100)        
KeyboardInterrupt

結論:SIGTERM trumps finally

33
unutbu

answer from nutb は、観察した動作が得られる理由whyを明確に説明しています。ただし、SIGTERMは、multiprocessing.pool._terminate_poolの実装方法のためにのみ送信されることを強調しておく必要があります。 Poolの使用を避けることができれば、希望する動作を得ることができます。これが 借りた例

from multiprocessing import Process
from time import sleep
import random

def f(x):
    try:
        sleep(random.random()*10)
        raise Exception
    except:
        print "Caught exception in process:", x
        # Make this last longer than the except clause in main.
        sleep(3)
    finally:
        print "Cleaning up process:", x

if __name__ == '__main__':
    processes = []
    for i in range(4):
        p = Process(target=f, args=(i,))
        p.start()
        processes.append(p)
    try:
        for process in processes:
            process.join()
    except:
        print "Caught exception in main."
    finally:
        print "Cleaning up main."

SIGINTを送信した後の出力例は、次のとおりです。

Caught exception in process: 0
^C
Cleaning up process: 0
Caught exception in main.
Cleaning up main.
Caught exception in process: 1
Caught exception in process: 2
Caught exception in process: 3
Cleaning up process: 1
Cleaning up process: 2
Cleaning up process: 3

finally句はすべてのプロセスに対して実行されることに注意してください。共有メモリが必要な場合は、QueuePipeManager、またはredissqlite3などの外部ストアの使用を検討してください。

4
Tom

finallyは元の例外を再発生させます それからreturnを除いて 。その後、例外はPool.mapによって発生し、アプリケーション全体を強制終了します。サブプロセスは終了し、他の例外は表示されません。

returnを追加して、例外を飲み込むことができます。

def Process(x):
  try:
    print x
    sleep(random.random())
    raise Exception('Exception: ' + x)
  finally:
    print 'Finally: ' + x
    return

次に、例外が発生したときのNone結果にmapが含まれている必要があります。

1
Jochen Ritzel