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)
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.
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 .
(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()