Python: многопроцессорная обработка.карта: Если один процесс вызывает исключение, почему не вызываются блоки 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'])

ожидаемый результат-это то, что для каждого из x, которое печатается самостоятельно строкой 8, есть должны быть вхождением 'Finally 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

Кажется, что исключение, завершающее один процесс, завершает Родительский и сестринский процессы, даже если есть дальнейшая работа требуются чтобы быть сделано в других процессах.

почему я ошибаюсь? Почему это правильно? Если это правильно, как должны безопасно очищать ресурсы в многопроцессорном Python?

3 ответов


короткий ответ: SIGTERM превосходит 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 suite отправляется SIGTERM.

рассмотрим следующий пример (test.py):

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

если вы запустите его, то отправьте его SIGTERM, затем процесс заканчивается немедленно, без ввода finally suite, о чем свидетельствует отсутствие вывода и отсутствие задержки.

в одном терминале:

% test.py

во втором терминале:

% pkill -TERM -f "test.py"

результат в первый терминал:

Terminated

сравните это с тем, что происходит, когда процесс направляется SIGINT (C-c):

во втором терминале:

% 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 превосходит finally.


на ответ С unutbu наверняка объясняет почему вы получите поведение, которое вы наблюдаете. Однако следует подчеркнуть, что SIGTERM отправляется только из-за того, как есть. Если вы можете избежать использования 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 is, пример вывода:

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

затем вы должны есть!--5--> в своем map результат при возникновении исключения.