Python multiprocessing-захват сигналов для перезапуска дочерних процессов или завершения родительского процесса
Я использую многопроцессорную библиотеку для создания двух дочерних процессов. Я хотел бы убедиться, что пока родительский процесс жив, если дочерние процессы умирают (получают SIGKILL или SIGTERM), они перезапускаются автоматически. С другой стороны, если родительский процесс получает SIGTERM/SIGINT, я хочу, чтобы он прекратил все дочерние процессы, а затем вышел.
вот как я подошел к проблеме:
import sys
import time
from signal import signal, SIGINT, SIGTERM, SIGQUIT, SIGCHLD, SIG_IGN
from functools import partial
import multiprocessing
import setproctitle
class HelloWorld(multiprocessing.Process):
def __init__(self):
super(HelloWorld, self).__init__()
# ignore, let parent handle it
signal(SIGTERM, SIG_IGN)
def run(self):
setproctitle.setproctitle("helloProcess")
while True:
print "Hello World"
time.sleep(1)
class Counter(multiprocessing.Process):
def __init__(self):
super(Counter, self).__init__()
self.counter = 1
# ignore, let parent handle it
signal(SIGTERM, SIG_IGN)
def run(self):
setproctitle.setproctitle("counterProcess")
while True:
print self.counter
time.sleep(1)
self.counter += 1
def signal_handler(helloProcess, counterProcess, signum, frame):
print multiprocessing.active_children()
print "helloProcess: ", helloProcess
print "counterProcess: ", counterProcess
if signum == 17:
print "helloProcess: ", helloProcess.is_alive()
if not helloProcess.is_alive():
print "Restarting helloProcess"
helloProcess = HelloWorld()
helloProcess.start()
print "counterProcess: ", counterProcess.is_alive()
if not counterProcess.is_alive():
print "Restarting counterProcess"
counterProcess = Counter()
counterProcess.start()
else:
if helloProcess.is_alive():
print "Stopping helloProcess"
helloProcess.terminate()
if counterProcess.is_alive():
print "Stopping counterProcess"
counterProcess.terminate()
sys.exit(0)
if __name__ == '__main__':
helloProcess = HelloWorld()
helloProcess.start()
counterProcess = Counter()
counterProcess.start()
for signame in [SIGINT, SIGTERM, SIGQUIT, SIGCHLD]:
signal(signame, partial(signal_handler, helloProcess, counterProcess))
multiprocessing.active_children()
Если я отправить сигнал SIGKILL к counterProcess, это перезагрузится правильно. Однако отправка SIGKILL в helloProcess также перезапускает контрпроцесс вместо helloProcess?
Если я отправлю SIGTERM родительскому процессу, родитель выйдет, но дочерние процессы станут сиротами и продолжатся. Как исправить это поведение?
2 ответов
воссоздать мертвых детей из signal.SIGCHLD
обработчик, мать должна вызвать одного из os.wait
функции, потому что Process.is_alive
здесь не работает.
Хотя это возможно, это сложно, потому что signal.SIGCHLD
доставляется матери, когда один из его детей меняет статус f.e. signal.SIGSTOP
, signal.SIGCONT
или любые другие завершающие сигналы принимаются ребенком.
Так что signal.SIGCHLD
обработчик должен различать эти состояния ребенка. Просто воссоздаю детей, когда signal.SIGCHLD
доставлен может создать больше детей, чем необходимо.
следующий код использует os.waitpid
С os.WNOHANG
чтобы сделать его неблокирующим и os.WUNTRACED
и os.WCONTINUED
для обучения, если signal.SIGCHLD
С signal.SIGSTOP
или signal.SIGCONT
.os.waitpid
не работает, т. е. возвращает (0, 0)
если Process
экземпляр print
Эд, я.е str(Process())
прежде чем позвонить os.waitpid
.
import sys
import time
from signal import signal, pause, SIGINT, SIGTERM, SIGQUIT, SIGCHLD, SIG_DFL
import multiprocessing
import os
class HelloWorld(multiprocessing.Process):
def run(self):
# reset SIGTERM to default for Process.terminate to work
signal(SIGTERM, SIG_DFL)
while True:
print "Hello World"
time.sleep(1)
class Counter(multiprocessing.Process):
def __init__(self):
super(Counter, self).__init__()
self.counter = 1
def run(self):
# reset SIGTERM to default for Process.terminate to work
signal(SIGTERM, SIG_DFL)
while True:
print self.counter
time.sleep(1)
self.counter += 1
def signal_handler(signum, _):
global helloProcess, counterProcess
if signum == SIGCHLD:
pid, status = os.waitpid(-1, os.WNOHANG|os.WUNTRACED|os.WCONTINUED)
if os.WIFCONTINUED(status) or os.WIFSTOPPED(status):
return
if os.WIFSIGNALED(status) or os.WIFEXITED(status):
if helloProcess.pid == pid:
print("Restarting helloProcess")
helloProcess = HelloWorld()
helloProcess.start()
elif counterProcess.pid == pid:
print("Restarting counterProcess")
counterProcess = Counter()
counterProcess.start()
else:
# mother shouldn't be notified when it terminates children
signal(SIGCHLD, SIG_DFL)
if helloProcess.is_alive():
print("Stopping helloProcess")
helloProcess.terminate()
if counterProcess.is_alive():
print("Stopping counterProcess")
counterProcess.terminate()
sys.exit(0)
if __name__ == '__main__':
helloProcess = HelloWorld()
helloProcess.start()
counterProcess = Counter()
counterProcess.start()
for signame in [SIGINT, SIGTERM, SIGQUIT, SIGCHLD]:
signal(signame, signal_handler)
while True:
pause()
следующий код воссоздает мертвых детей без использования signal.SIGCHLD
. Так это проще чем первый.
Создав двух детей, материнский процесс устанавливает обработчик сигнала с именем term_child
для SIGINT, SIGTERM, SIGQUIT. term_child
завершает работу и присоединяется к каждому ребенку при вызове.
материнский процесс продолжает проверять, живы ли дети, и воссоздает их при необходимости в while
петли.
потому что каждый ребенок наследует обработчики сигналов от матери,SIGINT
обработчик должен быть сброшен в значение по умолчанию для Process.terminate
для работы
import sys
import time
from signal import signal, SIGINT, SIGTERM, SIGQUIT
import multiprocessing
class HelloWorld(multiprocessing.Process):
def run(self):
signal(SIGTERM, SIG_DFL)
while True:
print "Hello World"
time.sleep(1)
class Counter(multiprocessing.Process):
def __init__(self):
super(Counter, self).__init__()
self.counter = 1
def run(self):
signal(SIGTERM, SIG_DFL)
while True:
print self.counter
time.sleep(1)
self.counter += 1
def term_child(_, __):
for child in children:
child.terminate()
child.join()
sys.exit(0)
if __name__ == '__main__':
children = [HelloWorld(), Counter()]
for child in children:
child.start()
for signame in (SIGINT, SIGTERM, SIGQUIT):
signal(signame, term_child)
while True:
for i, child in enumerate(children):
if not child.is_alive():
children[i] = type(child)()
children[i].start()
time.sleep(1)
есть несколько проблем с кодом, так что я пойду за ним в sequentailly.
если я отправить сигнал SIGKILL к counterProcess, это будет правильно перезагрузить. Однако отправка SIGKILL в helloProcess также перезапускает контрпроцесс вместо helloProcess?
это своеобразное поведение, скорее всего, связано с отсутствием блокировки вызова в вашем основном процессе, так как multiprocessing.active_children()
на самом деле не действует как один. Я не могу объяснить точная причина, почему программа ведет себя так, как она делает, но добавление блокировки вызова в , например.
while True:
time.sleep(1)
решает проблему.
еще одна довольно серьезная проблема заключается в том, как вы передаете объекты в обработчик:
helloProcess = HelloWorld()
...
partial(signal_handler, helloProcess, counterProcess)
что obsolate, учитывая, что вы создаете новые объекты внутри:
if not helloProcess.is_alive():
print "Restarting helloProcess"
helloProcess = HelloWorld()
helloProcess.start()
обратите внимание, что оба объекта используют различные псевдонимы для HelloWorld()
объекты. Частичный объект привязан к алиасу в , а объект в callback привязан к его псевдониму локальной области. Поэтому, назначая новый объект псевдониму локальной области, вы на самом деле не влияете на объект, к которому привязан обратный вызов (он по-прежнему привязан к объекту, созданному в __main__
scope).
вы можете исправить это, восстановив обратный вызов сигнала с новыми объектами таким же образом в области обратного вызова:
def signal_handler(...):
...
for signame in [SIGINT, SIGTERM, SIGQUIT, SIGCHLD]:
signal(signame, partial(signal_handler, helloProcess, counterProcess))
...
однако это приводит к другой ловушке, потому что теперь каждый дочерний процесс унаследует обратный вызов от родителя и получит доступ к нему каждый время приема сигналов. Чтобы исправить это, вы можете временно установить обработчики сигналов по умолчанию прямо перед созданием дочернего процесса:
for signame in [SIGINT, SIGTERM, SIGQUIT, SIGCHLD]:
signal(signame, SIG_DFL)
наконец, вы можете подавить любой сигнал, поступающий от ваших дочерних процессов, прежде чем завершить их, иначе они снова вызовут обратный вызов:
signal(SIGCHLD, SIG_IGN)
обратите внимание, что вы хотите изменить архитектуру своего приложения и использовать некоторые из функций multiprocessing
обеспечивает.
финал код:
import sys
import time
from signal import signal, SIGINT, SIGTERM, SIGQUIT, SIGCHLD, SIG_IGN, SIG_DFL
from functools import partial
import multiprocessing
#import setproctitle
class HelloWorld(multiprocessing.Process):
def __init__(self):
super(HelloWorld, self).__init__()
# ignore, let parent handle it
#signal(SIGTERM, SIG_IGN)
def run(self):
#setproctitle.setproctitle("helloProcess")
while True:
print "Hello World"
time.sleep(1)
class Counter(multiprocessing.Process):
def __init__(self):
super(Counter, self).__init__()
self.counter = 1
# ignore, let parent handle it
#signal(SIGTERM, SIG_IGN)
def run(self):
#setproctitle.setproctitle("counterProcess")
while True:
print self.counter
time.sleep(1)
self.counter += 1
def signal_handler(helloProcess, counterProcess, signum, frame):
print multiprocessing.active_children()
print "helloProcess: ", helloProcess
print "counterProcess: ", counterProcess
print "current_process: ", multiprocessing.current_process()
if signum == 17:
# Since each new child inherits current signal handler,
# temporarily set it to default before spawning new child.
for signame in [SIGINT, SIGTERM, SIGQUIT, SIGCHLD]:
signal(signame, SIG_DFL)
print "helloProcess: ", helloProcess.is_alive()
if not helloProcess.is_alive():
print "Restarting helloProcess"
helloProcess = HelloWorld()
helloProcess.start()
print "counterProcess: ", counterProcess.is_alive()
if not counterProcess.is_alive():
print "Restarting counterProcess"
counterProcess = Counter()
counterProcess.start()
# After new children are spawned, revert to old signal handling policy.
for signame in [SIGINT, SIGTERM, SIGQUIT, SIGCHLD]:
signal(signame, partial(signal_handler, helloProcess, counterProcess))
else:
# Ignore any signal that child communicates before quit
signal(SIGCHLD, SIG_IGN)
if helloProcess.is_alive():
print "Stopping helloProcess"
helloProcess.terminate()
if counterProcess.is_alive():
print "Stopping counterProcess"
counterProcess.terminate()
sys.exit(0)
if __name__ == '__main__':
helloProcess = HelloWorld()
helloProcess.start()
counterProcess = Counter()
counterProcess.start()
for signame in [SIGINT, SIGTERM, SIGQUIT, SIGCHLD]:
signal(signame, partial(signal_handler, helloProcess, counterProcess))
while True:
print multiprocessing.active_children()
time.sleep(1)