web-dev-qa-db-fra.com

Catch Ctrl + C / SIGINT et quitter multiprocesses gracieusement dans python

Comment attraper un Ctrl + C en multiprocessus python et quitter tous les processus en douceur), j'ai besoin de la solution pour fonctionner à la fois sous Unix et Windows. J'ai essayé ce qui suit:

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 __== "__main__":
    for i in range(50):
        p = multiprocessing.Process(target=worker)
        jobs.append(p)
        p.start()

Et cela fonctionne bien, mais je ne pense pas que ce soit la bonne solution.

EDIT: Ceci pourrait être un doublon de celui-ci

76
zenpoy

La solution précédemment acceptée a des conditions de concurrence et ne fonctionne pas avec les fonctions map et async.

La bonne façon de gérer Ctrl + C/SIGINT avec multiprocessing.Pool est de:

  1. Faites en sorte que le processus ignore SIGINT avant qu'un processus Pool ne soit créé. De cette façon, les processus enfants créés héritent du gestionnaire SIGINT.
  2. Restaurez le gestionnaire SIGINT d'origine dans le processus parent après la création d'un Pool.
  3. Utilisation map_async et apply_async au lieu de bloquer map et apply.
  4. Attendez les résultats avec timeout car l'attente de blocage par défaut ignore tous les signaux. C'est Python bug https://bugs.python.org/issue8296 .

Mettre ensemble:

#!/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 __== "__main__":
    main()

Comme @YakovShklarov l'a noté, il existe un intervalle de temps entre ignorer le signal et le ne pas ignorer dans le processus parent, au cours duquel le signal peut être perdu. En utilisant pthread_sigmask au lieu de bloquer temporairement la livraison du signal dans le processus parent empêcherait la perte du signal, cependant, il n'est pas disponible dans Python-2.

69
Maxim Egorushkin

La solution est basée sur ce lien et ce lien et le problème a été résolu, je devais passer à Pool bien que:

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 __== "__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()
33
zenpoy

Il vous suffit de gérer les exceptions KeyboardInterrupt-SystemExit dans votre processus de travail:

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