私の理解では、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でリソースを安全にクリーンアップしますか?
短い答え: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
の各pool
はmultiprocessing.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
プロセスがSIGINT
(C-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
。
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
句はすべてのプロセスに対して実行されることに注意してください。共有メモリが必要な場合は、Queue
、Pipe
、Manager
、またはredis
やsqlite3
などの外部ストアの使用を検討してください。
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
が含まれている必要があります。