web-dev-qa-db-de.com

zombie-Prozesse mit dem Subprozess-Modul beenden (oder vermeiden)

Wenn ich ein Python-Skript aus einem anderen Python-Skript mit dem Unterprozess-Modul starte, wird ein Zombie-Prozess erstellt, wenn der Unterprozess "abgeschlossen" ist. Ich kann diesen Unterprozess nicht töten, es sei denn, ich bringe meinen Python-Prozess für Eltern ab. 

Gibt es eine Möglichkeit, den Unterprozess zu töten, ohne die Eltern zu töten? Ich weiß, dass ich dies mit wait () tun kann, aber ich muss mein Skript mit no_wait () ausführen.

45
Dave

Wenn Sie Popen.communicate() oder call() nicht verwenden, wird dies zu einem Zombie-Prozess führen.

Wenn Sie die Ausgabe des Befehls nicht benötigen, können Sie subprocess.call() verwenden:

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

Wenn die Ausgabe wichtig ist, sollten Sie Popen() und communicate() verwenden, um stdout und stderr zu erhalten.

>>> 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

Ein Zombie-Prozess ist kein echter Prozess. Es ist nur ein verbleibender Eintrag in der Prozesstabelle, bis der übergeordnete Prozess den Rückkehrcode des Kindes anfordert. Der eigentliche Prozess ist beendet und erfordert keine anderen Ressourcen als den Eintrag der Prozesstabelle.

Wir benötigen wahrscheinlich mehr Informationen zu den Prozessen, die Sie ausführen, um tatsächlich mehr zu helfen.

Falls Ihr Python-Programm jedoch weiß wenn die untergeordneten Prozesse beendet sind (z. B. durch Erreichen der untergeordneten stdout-Daten), können Sie process.wait() sicher aufrufen:

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') )

Beispielausgabe:

$ 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

Andernfalls können Sie alle Kinder in einer Liste und ab und an .poll für ihre Rückgabecodes halten. Denken Sie daran, nach jeder Iteration die untergeordneten Elemente mit Rückgabecodes von None (d. H. Die fertigen) von der Liste zu entfernen.

19
tzot

Wenn Sie das Unterprozessobjekt löschen, verwenden Sie del , um die Garbage Collection zu erzwingen. Dies führt dazu, dass das Unterprozessobjekt gelöscht wird und die nicht mehr vorhandenen Prozesse abgebrochen werden, ohne Ihren Interpreter zu beenden. Sie können dies zunächst in der Python-Befehlszeilenschnittstelle ausprobieren.

15
user331905

Die Python-Laufzeitumgebung übernimmt die Verantwortung für die Beseitigung des Zombie-Prozesses, sobald die Prozessobjekte abgefallen sind. Wenn Sie den Zombie herumliegen sehen, bedeutet dies, dass Sie ein Prozessobjekt behalten und nicht aufgerufen haben, warten, abfragen oder beenden.

5
Peter

Wenn Sie einfach subprocess.Popen verwenden, ist alles in Ordnung - wie folgt:

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

Sie können .poll() für das von Popen zurückgegebene Objekt verwenden, um zu überprüfen, ob das Objekt beendet wurde (ohne zu warten). Wenn None zurückgegeben wird, läuft das Kind noch.

Stellen Sie sicher, dass Sie keine Verweise auf die Popen-Objekte behalten. Andernfalls wird kein Müll gesammelt, sodass Sie mit Zombies enden. Hier ist ein Beispiel:

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

Wenn Sie im obigen Beispiel die Zombies loswerden möchten, können Sie entweder .wait() für jedes der untergeordneten Elemente oder .poll() verwenden, bis das Ergebnis nicht None ist.

So oder so ist es gut - entweder keine Referenzen beibehalten oder .wait() oder .poll() verwenden.

5
ibz

Ich bin nicht sicher, was Sie meinen "Ich muss mein Skript mit no_wait () ausführen", aber ich denke, dieses Beispiel macht das, was Sie brauchen. Prozesse werden nicht sehr lange Zombies sein. Der übergeordnete Prozess wird nur dann wait() für sie verwendet, wenn sie tatsächlich bereits beendet sind, und wird daher schnell entkomprimiert.

#!/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"

Die Ausgabe gegen Ende sieht folgendermaßen aus:

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

Ich bin mir nicht ganz sicher, was Sie mit no_wait() meinen. Meinen Sie damit, Sie können das Warten auf untergeordnete Prozesse nicht blockieren? Angenommen, ich denke, das wird tun, was Sie wollen:

os.wait3(os.WNOHANG)
0

Vor kurzem bin ich aufgrund meines Python-Skripts auf dieses Zombie-Problem gestoßen. Das eigentliche Problem war hauptsächlich auf das Töten des Unterprozesses zurückzuführen, und der übergeordnete Prozess weiß nicht, dass das Kind tot ist. Was ich also tat, war das Hinzufügen von popen.communicate () nach dem Kill-Signal des Kindprozesses, so dass der Elternprozess erkennt, dass das Kind tot ist. Der Kernel aktualisiert dann die PID des Kindprozesses, da das Kind nicht mehr und also gibt es jetzt keine zombies.

PS: poll ist auch hier eine Option, da der Elternstatus über den untergeordneten Status geprüft wird. In Unterprozessen ist es oft besser, wenn Sie check_output verwenden oder anrufen, wenn Sie nicht mit stdout und stdin kommunizieren müssen.