Поймай Ctrl+C / SIGINT и аккуратно выйди из мультипроцесса в python
Как мне поймать Ctrl+C в многопроцессорной Python-программе и корректно завершить все процессы, мне нужно решение для работы как на Unix, так и на Windows. Я пробовал следующее:
import multiprocessing
import time
import signal
import sys
jobs = []
def worker():
signal.signal(signal.SIGINT, signal_handler)
while(True):
time.sleep(1.1234)
print "Working..."
def signal_handler(signal, frame):
print 'You pressed Ctrl+C!'
# for p in jobs:
# p.terminate()
sys.exit(0)
if __name__ == "__main__":
for i in range(50):
p = multiprocessing.Process(target=worker)
jobs.append(p)
p.start()
И это отчасти работает, но я не думаю, что это правильное решение.
РЕДАКТИРОВАТЬ: Это может быть дубликатом этого
3 ответа
Ранее принятое решение имеет гоночные условия и не работает с map
а также async
функции.
Правильный способ обработки Ctrl+C/SIGINT
с multiprocessing.Pool
это:
- Заставь процесс игнорировать
SIGINT
перед процессомPool
создано. Таким образом созданные дочерние процессы наследуютSIGINT
обработчик. - Восстановить оригинал
SIGINT
обработчик в родительском процессе послеPool
был создан. - использование
map_async
а такжеapply_async
вместо блокировкиmap
а такжеapply
, - Ожидание результатов с таймаутом, потому что блокировка по умолчанию ожидает игнорировать все сигналы. Это ошибка Python https://bugs.python.org/issue8296.
Собираем это вместе:
#!/bin/env python
from __future__ import print_function
import multiprocessing
import os
import signal
import time
def run_worker(delay):
print("In a worker process", os.getpid())
time.sleep(delay)
def main():
print("Initializng 2 workers")
original_sigint_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
pool = multiprocessing.Pool(2)
signal.signal(signal.SIGINT, original_sigint_handler)
try:
print("Starting 2 jobs of 5 seconds each")
res = pool.map_async(run_worker, [5, 5])
print("Waiting for results")
res.get(60) # Without the timeout this blocking call ignores all signals.
except KeyboardInterrupt:
print("Caught KeyboardInterrupt, terminating workers")
pool.terminate()
else:
print("Normal termination")
pool.close()
pool.join()
if __name__ == "__main__":
main()
Как отметил @YakovShklarov, существует промежуток времени между игнорированием сигнала и игнорированием его в родительском процессе, в течение которого сигнал может быть потерян. С помощью pthread_sigmask
вместо этого временная блокировка доставки сигнала в родительском процессе предотвратит потерю сигнала, однако в Python-2 он недоступен.
Решение основано на этой ссылке и этой ссылке, и это решило проблему, я должен был перейти к Pool
хоть:
import multiprocessing
import time
import signal
import sys
def init_worker():
signal.signal(signal.SIGINT, signal.SIG_IGN)
def worker():
while(True):
time.sleep(1.1234)
print "Working..."
if __name__ == "__main__":
pool = multiprocessing.Pool(50, init_worker)
try:
for i in range(50):
pool.apply_async(worker)
time.sleep(10)
pool.close()
pool.join()
except KeyboardInterrupt:
print "Caught KeyboardInterrupt, terminating workers"
pool.terminate()
pool.join()
Просто обработайте исключения KeyboardInterrupt-SystemExit в вашем рабочем процессе:
def worker():
while(True):
try:
msg = self.msg_queue.get()
except (KeyboardInterrupt, SystemExit):
print("Exiting...")
break