Подтвердить что ты не робот

Поймать 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()

И это вроде работает, но я не думаю, что это правильное решение.

EDIT: Это может быть дубликат этого

4b9b3361

Ответ 1

Ранее принятое решение имеет условия гонки и не работает с функциями map и async.

Правильный способ обработки Ctrl + C/ SIGINT с помощью multiprocessing.Pool:

  • Перед тем, как будет создан процесс Pool, заставьте процесс игнорировать SIGINT. Таким образом, созданные дочерние процессы наследуют обработчик 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()

Как заметил Яков Шкларов, есть окно времени между игнорированием сигнала и неименованием его в родительском процессе, в течение которого сигнал может быть потерян. Использование pthread_sigmask вместо того, чтобы временно заблокировать доставку сигнала в родительском процессе, не позволит потерять сигнал, однако он недоступен в Python-2.

Ответ 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()

Ответ 3

Просто обрабатывайте исключения KeyboardInterrupt-SystemExit в рабочем процессе:

def worker():
    while(True):
        try:
            msg = self.msg_queue.get()
        except (KeyboardInterrupt, SystemExit):
            print("Exiting...")
            break