Прерывание клавиатуры с многопроцессорной обработкой Python

У меня возникли проблемы изящной обработки прерывания клавиатуры с мульти-обработки Python

(Да, я знаю, что Ctr-C не должен гарантировать постепенное отключение - но давайте оставим это обсуждение для другой темы)

Рассмотрим следующий код, где я являюсь пользователем multiprocessing.Manager#list() который является ListProxy, который, как я понял, обрабатывает многопроцессный доступ к списку.

Когда я Ctr-C из этого - я получаю socket.error: [Errno 2] No such file or directory при попытке получить доступ к ListProxy

Я хотел бы, чтобы общий список не был поврежден при Ctr-C. Это возможно?!

Примечание: я хочу решить эту проблему без использования пулов и очередей.

from multiprocessing import Process, Manager
from time import sleep

def f(process_number, shared_array):
    try:
        print "starting thread: ", process_number
        shared_array.append(process_number)
        sleep(3)
        shared_array.append(process_number)
    except KeyboardInterrupt:
        print "Keyboard interrupt in process: ", process_number
    finally:
        print "cleaning up thread", process_number

if __name__ == '__main__':

    processes = []

    manager = Manager()
    shared_array = manager.list()

    for i in xrange(4):
        p = Process(target=f, args=(i, shared_array))
        p.start()
        processes.append(p)

    try:
        for process in processes:
            process.join()
    except KeyboardInterrupt:
        print "Keyboard interrupt in main"

    for item in shared_array:
        # raises "socket.error: [Errno 2] No such file or directory"
        print item

Если вы запустите это, а затем нажмете Ctr-C, мы получим следующее:

starting thread:  0
starting thread:  1
starting thread:  3
starting thread:  2
^CKeyboard interupt in process:  3
Keyboard interupt in process:  0
cleaning up thread 3
cleaning up thread 0
Keyboard interupt in process:  1
Keyboard interupt in process:  2
cleaning up thread 1
cleaning up thread 2
Keyboard interupt in main
Traceback (most recent call last):
  File "multi.py", line 33, in <module>
    for item in shared_array:
  File "<string>", line 2, in __getitem__
  File "/usr/local/Cellar/python/2.7.3/Frameworks/Python.framework/Versions/2.7/lib/python2.7/multiprocessing/managers.py", line 755, in _callmethod
    self._connect()
  File "/usr/local/Cellar/python/2.7.3/Frameworks/Python.framework/Versions/2.7/lib/python2.7/multiprocessing/managers.py", line 742, in _connect
    conn = self._Client(self._token.address, authkey=self._authkey)
  File "/usr/local/Cellar/python/2.7.3/Frameworks/Python.framework/Versions/2.7/lib/python2.7/multiprocessing/connection.py", line 169, in Client
    c = SocketClient(address)
  File "/usr/local/Cellar/python/2.7.3/Frameworks/Python.framework/Versions/2.7/lib/python2.7/multiprocessing/connection.py", line 293, in SocketClient
    s.connect(address)
  File "/usr/local/Cellar/python/2.7.3/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py", line 224, in meth
    return getattr(self._sock,name)(*args)
socket.error: [Errno 2] No such file or directory

(Вот еще один подход, использующий multiprocessing.Lock с похожим аффектом... gist) Похожие вопросы:

2 ответа

Решение

multiprocessing.Manager() запускает дочерний процесс, который отвечает за обработку вашего прокси общего списка.

вывод netstat во время работы:

unix 2 [ACC] LISTENING STREAM 3921657 8457/python /tmp/pymp-B9dcij/listener-X423Ml

этот дочерний процесс, созданный multiprocessing.Manager() перехватывает ваш SIGINT и завершает работу, вызывая разыменование всего, что с ним связано, и, следовательно, вашу ошибку "нет такого файла" (я также получил несколько других ошибок в зависимости от того, когда я решил отправить SIGINT).

Чтобы решить эту проблему, вы можете напрямую объявить объект SyncManager (вместо того, чтобы позволить Manager () сделать это за вас). это потребует от вас использования метода start () для фактического запуска дочернего процесса. метод start () принимает функцию инициализации в качестве первого аргумента (вы можете переопределить SIGINT для менеджера здесь).

код ниже, попробуйте:

from multiprocessing import Process, Manager
from multiprocessing.managers import BaseManager, SyncManager
from time import sleep
import signal

#handle SIGINT from SyncManager object
def mgr_sig_handler(signal, frame):
    print 'not closing the mgr'

#initilizer for SyncManager
def mgr_init():
    signal.signal(signal.SIGINT, mgr_sig_handler)
    #signal.signal(signal.SIGINT, signal.SIG_IGN) # <- OR do this to just ignore the signal
    print 'initialized mananger'

def f(process_number, shared_array):
    try:
        print "starting thread: ", process_number
        shared_array.append(process_number)
        sleep(3)
        shared_array.append(process_number)
    except KeyboardInterrupt:
        print "Keyboard interrupt in process: ", process_number
    finally:
        print "cleaning up thread", process_number

if __name__ == '__main__':

    processes = []

     #using syncmanager directly instead of letting Manager() do it for me
    manager = SyncManager()
    manager.start(mgr_init)  #fire up the child manager process
    shared_array = manager.list()

    for i in xrange(4):
        p = Process(target=f, args=(i, shared_array))
        p.start()
        processes.append(p)

    try:
        for process in processes:
            process.join()
    except KeyboardInterrupt:
        print "Keyboard interrupt in main"

    for item in shared_array:
        print item

Как я отвечу на аналогичный вопрос ( дубликат):

Самое простое решение - начать менеджер с

manager.start(signal.signal, (signal.SIGINT, signal.SIG_IGN))

вместо manager.start(). И проверьте, есть ли сигнальный модуль в вашем импорте (импорт сигнала).

Это ловит и игнорирует SIGINT (Ctrl-C) в процессе менеджера.

Другие вопросы по тегам