web-dev-qa-db-fra.com

comment terminer le processus en utilisant le multiprocessing de python

J'ai du code qui doit s'exécuter sur plusieurs autres systèmes qui peuvent se bloquer ou avoir des problèmes hors de mon contrôle. Je voudrais utiliser le multiprocessing de python pour générer des processus enfants pour qu'ils s'exécutent indépendamment du programme principal, puis quand ils se bloquent ou ont des problèmes, arrêtez-les, mais je ne suis pas sûr de la meilleure façon de procéder.

Lorsque terminate est appelé, il tue le processus enfant, mais il devient alors un zombie défunt qui n'est pas libéré tant que l'objet de processus n'est pas parti. L'exemple de code ci-dessous, où la boucle ne se termine jamais, fonctionne pour la tuer et permettre une réapparition lorsqu'elle est appelée à nouveau, mais ne semble pas être une bonne façon de procéder (c.-à-d. Multiprocessing.Process () serait mieux dans le __init __ ()).

Quelqu'un a une suggestion?

class Process(object):
    def __init__(self):
        self.thing = Thing()
        self.running_flag = multiprocessing.Value("i", 1)

    def run(self):
        self.process = multiprocessing.Process(target=self.thing.worker, args=(self.running_flag,))
        self.process.start()
        print self.process.pid

    def pause_resume(self):
        self.running_flag.value = not self.running_flag.value

    def terminate(self):
        self.process.terminate()

class Thing(object):
    def __init__(self):
        self.count = 1

    def worker(self,running_flag):
        while True:
            if running_flag.value:
                self.do_work()

    def do_work(self):
        print "working {0} ...".format(self.count)
        self.count += 1
        time.sleep(1)
15
Dan Littlejohn

Vous pouvez exécuter les processus enfants en tant que démons en arrière-plan.

process.daemon = True

Toute erreur et blocage (ou boucle infinie) dans un processus démon n'affectera pas le processus principal, et il ne sera terminé qu'une fois le processus principal terminé.

Cela fonctionnera pour des problèmes simples jusqu'à ce que vous rencontriez de nombreux processus de démon enfant qui continueront à récolter des souvenirs du processus parent sans aucun contrôle explicite.

Le meilleur moyen est de configurer un Queue pour que tous les processus enfants communiquent avec le processus parent afin que nous puissions join et les nettoyer correctement. Voici un code simple qui vérifiera si un traitement enfant est bloqué (aka time.sleep(1000)), et enverra un message à la file d'attente pour que le processus principal prenne des mesures:

import multiprocessing as mp
import time
import queue

running_flag = mp.Value("i", 1)

def worker(running_flag, q):
    count = 1
    while True:
        if running_flag.value:
            print "working {0} ...".format(count)
            count += 1
            q.put(count)
            time.sleep(1)
            if count > 3:
                # Simulate hanging with sleep
                print "hanging..."
                time.sleep(1000)

def watchdog(q):
    """
    This check the queue for updates and send a signal to it
    when the child process isn't sending anything for too long
    """
    while True:
        try:
            msg = q.get(timeout=10.0)
        except queue.Empty as e:
            print "[WATCHDOG]: Maybe WORKER is slacking"
            q.put("KILL WORKER")

def main():
    """The main process"""
    q = mp.Queue()

    workr = mp.Process(target=worker, args=(running_flag, q))
    wdog = mp.Process(target=watchdog, args=(q,))

    # run the watchdog as daemon so it terminates with the main process
    wdog.daemon = True

    workr.start()
    print "[MAIN]: starting process P1"
    wdog.start()

    # Poll the queue
    while True:
        msg = q.get()
        if msg == "KILL WATCHDOG":
            print "[MAIN]: Terminating slacking WORKER"
            workr.terminate()
            time.sleep(0.1)
            if not workr.is_alive():
                print "[MAIN]: WORKER is a goner"
                workr.join(timeout=1.0)
                print "[MAIN]: Joined WORKER successfully!"
                q.close()
                break # watchdog process daemon gets terminated

if __name__ == '__main__':
    main()

Sans terminer worker, une tentative de join() vers le processus principal aurait été bloquée pour toujours puisque worker n'est jamais terminée.

8
Pie 'Oh' Pah

La façon dont Python multiprocessing gère les processus est un peu déroutante.

D'après les directives de multitraitement:

Rejoindre des processus zombies

Sous Unix, lorsqu'un processus se termine mais n'a pas été joint, il devient un zombie. Il ne devrait jamais y en avoir beaucoup car chaque fois qu'un nouveau processus démarre (ou que active_children () est appelé) tous les processus terminés qui n'ont pas encore été joints seront joints. Appelant également Process.is_alive d'un processus terminé se joindra au processus. Néanmoins, il est probablement recommandé de joindre explicitement tous les processus que vous démarrez.

Afin d'éviter qu'un processus ne devienne un zombie, vous devez appeler sa méthode join() une fois que vous l'avez tué.

Si vous voulez un moyen plus simple de gérer les appels suspendus dans votre système, vous pouvez jeter un œil à caillo .

6
noxdafox

(N'ayant pas assez de points de réputation pour commenter, voici une réponse complète)

@PieOhPah: merci pour cet très bel exemple.
Malheureusement, il n'y a qu'un petit défaut qui ne laisse pas le chien de garde tuer le travailleur:

if msg == "KILL WATCHDOG":

cA devrait etre:

if msg == "KILL WORKER":

Donc le code devient (avec print mis à jour pour python3):

import multiprocessing as mp
import time
import queue

running_flag = mp.Value("i", 1)

def worker(running_flag, q):
    count = 1
    while True:
        if running_flag.value:
            print ("working {0} ...".format(count))
            count += 1
            q.put(count)
            time.sleep(1)
            if count > 3:
                # Simulate hanging with sleep
                print ("hanging...")
                time.sleep(1000)

def watchdog(q):
    """
    This check the queue for updates and send a signal to it
    when the child process isn't sending anything for too long
    """
    while True:
        try:
            msg = q.get(timeout=10.0)
        except queue.Empty as e:
            print ("[WATCHDOG]: Maybe WORKER is slacking")
            q.put("KILL WORKER")

def main():
    """The main process"""
    q = mp.Queue()

    workr = mp.Process(target=worker, args=(running_flag, q))
    wdog = mp.Process(target=watchdog, args=(q,))

    # run the watchdog as daemon so it terminates with the main process
    wdog.daemon = True

    workr.start()
    print ("[MAIN]: starting process P1")
    wdog.start()

    # Poll the queue
    while True:
        msg = q.get()
#        if msg == "KILL WATCHDOG":
        if msg == "KILL WORKER":
            print ("[MAIN]: Terminating slacking WORKER")
            workr.terminate()
            time.sleep(0.1)
            if not workr.is_alive():
                print ("[MAIN]: WORKER is a goner")
                workr.join(timeout=1.0)
                print ("[MAIN]: Joined WORKER successfully!")
                q.close()
                break # watchdog process daemon gets terminated

if __name__ == '__main__':
    main()
3
Leo