web-dev-qa-db-fra.com

comment tuer (ou éviter) les processus zombies avec le module de sous-processus

Lorsque je lance un script python depuis un autre script utilisant le module de sous-processus, un processus zombie est créé lorsque le sous-processus "est terminé". Je suis incapable de tuer ce sous-processus à moins de tuer mon processus python parent. 

Existe-t-il un moyen de tuer le sous-processus sans tuer le parent? Je sais que je peux le faire en utilisant wait (), mais je dois exécuter mon script avec no_wait ().

45
Dave

Ne pas utiliser Popen.communicate() ou call() entraînera un processus zombie.

Si vous n'avez pas besoin de la sortie de la commande, vous pouvez utiliser subprocess.call():

>>> import subprocess
>>> subprocess.call(['grep', 'jdoe', '/etc/passwd'])
0

Si la sortie est importante, vous devez utiliser Popen() et communicate() pour obtenir les sorties standard et stderr.

>>> from subprocess import Popen, PIPE
>>> process = Popen(['ls', '-l', '/tmp'], stdout=PIPE, stderr=PIPE)
>>> stdout, stderr = process.communicate()
>>> stderr
''
>>> print stdout
total 0
-rw-r--r-- 1 jdoe jdoe 0 2010-05-03 17:05 bar
-rw-r--r-- 1 jdoe jdoe 0 2010-05-03 17:05 baz
-rw-r--r-- 1 jdoe jdoe 0 2010-05-03 17:05 foo
22
David Narayan

Un processus zombie n'est pas un processus réel; il ne reste qu'une entrée dans la table des processus jusqu'à ce que le processus parent demande le code de retour de l'enfant. Le processus réel est terminé et ne nécessite aucune autre ressource que ladite entrée de table de processus.

Nous avons probablement besoin de plus d’informations sur les processus que vous exécutez afin d’aider réellement davantage.

Cependant, dans le cas où votre programme Python sait que les processus enfants sont terminés (par exemple, en atteignant la fin des données de la fille stdout), vous pouvez appeler en toute sécurité process.wait():

import subprocess

process= subprocess.Popen( ('ls', '-l', '/tmp'), stdout=subprocess.PIPE)

for line in process.stdout:
        pass

subprocess.call( ('ps', '-l') )
process.wait()
print "after wait"
subprocess.call( ('ps', '-l') )

Exemple de sortie:

$ python so2760652.py
F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 S   501 21328 21326  0  80   0 -  1574 wait   pts/2    00:00:00 bash
0 S   501 21516 21328  0  80   0 -  1434 wait   pts/2    00:00:00 python
0 Z   501 21517 21516  0  80   0 -     0 exit   pts/2    00:00:00 ls <defunct>
0 R   501 21518 21516  0  80   0 -   608 -      pts/2    00:00:00 ps
after wait
F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 S   501 21328 21326  0  80   0 -  1574 wait   pts/2    00:00:00 bash
0 S   501 21516 21328  0  80   0 -  1467 wait   pts/2    00:00:00 python
0 R   501 21519 21516  0  80   0 -   608 -      pts/2    00:00:00 ps

Sinon, vous pouvez conserver tous les enfants dans une liste et maintenant .poll pour leurs codes de retour. Après chaque itération, n'oubliez pas de supprimer de la liste les enfants dont le code de retour est différent de None (c'est-à-dire ceux qui sont finis).

19
tzot

Si vous supprimez l'objet de sous-processus à l'aide de del pour forcer le nettoyage de la mémoire, le sous-processus sera supprimé, puis les processus obsolètes disparaîtront sans mettre fin à votre interprète. Vous pouvez d’abord essayer ceci dans l’interface de ligne de commande Python.

15
user331905

Le runtime Python prend la responsabilité de se débarrasser des processus zombies une fois que leurs objets de processus ont été collectés. Si vous voyez le zombie traîner, cela signifie que vous avez conservé un objet de processus et que vous n'avez pas appelé attendre, interroger ou y mettre fin.

5
Peter

Si vous utilisez simplement subprocess.Popen, tout ira bien - voici comment:

import subprocess

def spawn_some_children():
    subprocess.Popen(["sleep", "3"])
    subprocess.Popen(["sleep", "3"])
    subprocess.Popen(["sleep", "3"])

def do_some_stuff():
    spawn_some_children()
    # do some stuff
    print "children went out to play, now I can do my job..."
    # do more stuff

if __== '__main__':
    do_some_stuff()

Vous pouvez utiliser .poll() sur l'objet renvoyé par Popen pour vérifier s'il est terminé (sans attendre). S'il renvoie None, l'enfant est toujours en cours d'exécution.

Assurez-vous de ne pas conserver de références aux objets Popen. Si vous les gardez, ils ne seront pas ramassés, ainsi vous vous retrouverez avec des zombies. Voici un exemple:

import subprocess

def spawn_some_children():
    children = []
    children.append(subprocess.Popen(["sleep", "3"]))
    children.append(subprocess.Popen(["sleep", "3"]))
    children.append(subprocess.Popen(["sleep", "3"]))
    return children

def do_some_stuff():
    children = spawn_some_children()
    # do some stuff
    print "children went out to play, now I can do my job..."
    # do more stuff

    # if children finish while we are in this function,
    # they will become zombies - because we keep a reference to them

Dans l'exemple ci-dessus, si vous souhaitez vous débarrasser des zombies, vous pouvez utiliser .wait() sur chacun des enfants ou .poll() jusqu'à ce que le résultat ne soit plus None.

Dans les deux cas, tout va bien - soit ne pas conserver de références, soit utiliser .wait() ou .poll().

5
ibz

Je ne suis pas sûr de comprendre ce que vous voulez dire par "Je dois exécuter mon script avec no_wait ()", mais je pense que cet exemple répond à vos besoins. Les processus ne seront pas des zombies très longtemps. Le processus parent n’appliquera que wait() quand ils sont déjà terminés et ils seront donc rapidement zombés.

#!/usr/bin/env python2.6
import subprocess
import sys
import time

children = []
#Step 1: Launch all the children asynchronously
for i in range(10):
    #For testing, launch a subshell that will sleep various times
    popen = subprocess.Popen(["/bin/sh", "-c", "sleep %s" % (i + 8)])
    children.append(popen)
    print "launched subprocess PID %s" % popen.pid

#reverse the list just to prove we wait on children in the order they finish,
#not necessarily the order they start
children.reverse()
#Step 2: loop until all children are terminated
while children:
    #Step 3: poll all active children in order
    children[:] = [child for child in children if child.poll() is None]
    print "Still running: %s" % [popen.pid for popen in children]
    time.sleep(1)

print "All children terminated"

La sortie vers la fin ressemble à ceci:

Still running: [29776, 29774, 29772]
Still running: [29776, 29774]
Still running: [29776]
Still running: []
All children terminated
1
Peter Lyons

Je ne suis pas tout à fait sûr de ce que vous entendez par no_wait(). Voulez-vous dire que vous ne pouvez pas bloquer l'attente de la fin des processus enfants? En supposant que, je pense que cela va faire ce que vous voulez:

os.wait3(os.WNOHANG)
0

Récemment, j'ai rencontré ce problème de zombie en raison de mon script python. Le problème réel était principalement dû à la suppression du sous-processus et le processus parent ne savait pas que l'enfant était mort. Donc, ce que j’ai fait, c’est simplement d’ajouter popen.communicate () après le signal de suppression du processus enfant afin que le processus parent sache que l’enfant est mort, puis le noyau met à jour le pid de donc il n'y a pas de zombies formés maintenant.

PS: poll est également une option car il vérifie et transmet le statut de l’enfant au parent. Souvent, dans les sous-processus, il est préférable d'utiliser u check_output ou d'appeler si vous n'avez pas besoin de communiquer avec les commandes stdout et stdin.