2010-05-03 19 views
40

Cuando inicio un script de python desde otro script de python usando el módulo de subproceso, se crea un proceso zombie cuando el subproceso se "completa". No puedo matar este subproceso a menos que mate mi proceso padre de python.cómo matar (o evitar) procesos zombie con módulo de subproceso

¿Hay alguna manera de matar el subproceso sin matar al elemento principal? Sé que puedo hacer esto usando wait(), pero necesito ejecutar mi script con no_wait().

Respuesta

0

No estoy del todo seguro de lo que quieres decir con no_wait(). ¿Quiere decir que no puede bloquear la espera de procesos secundarios para finalizar? Asumiendo así, creo que esto va a hacer lo que quiere:

os.wait3(os.WNOHANG) 
17

No usar Popen.communicate() o call() dará lugar a un proceso de zombi.

Si usted no necesita la salida del comando, puede utilizar subprocess.call():

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

Si la salida es importante, se debe utilizar Popen() y communicate() para obtener el stdout y 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 
+1

Gracias por su comentario. Sin pretenderlo, la comunicación espera a que los procesos se completen antes de generar uno nuevo. Necesito ejecutar procesos numéricos en paralelo. – Dave

+1

call() espera a que finalice el proceso. Creo que OP quiere generar procesos y hacer algo más en el proceso principal mientras se ejecutan. – ibz

+4

Con 'Popen.poll()' puede verificar si el proceso ha finalizado, entonces puede usar 'communicate'. De esta manera puede mantener la concurrencia. –

16

Un proceso zombie no es un proceso real; es solo una entrada restante en la tabla de proceso hasta que el proceso principal solicita el código de retorno del niño. El proceso real ha finalizado y no requiere otros recursos, pero dicha entrada en la tabla de proceso.

Probablemente necesitemos más información sobre los procesos que ejecuta para realmente ayudar más.

Sin embargo, en el caso de que su programa Python sabe cuando los procesos hijos han terminado (por ejemplo, al llegar a la final de los datos stdout niño), entonces se puede llamar de forma segura 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')) 

Ejemplo salida:

$ 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 

de lo contrario, puede mantener a todos los niños en una lista, y de vez en .poll por sus códigos de retorno. Después de cada iteración, recuerde eliminar de la lista a los niños con códigos de retorno diferentes a None (es decir, los terminados).

+0

Oh, ahora veo que mi respuesta es básicamente la versión del código de su último párrafo. –

+0

Información muy útil sobre los procesos zombies. ¿Sabes si esta información se aplica por igual a todas las plataformas? es decir, que los procesos zombies son simplemente entradas y no se están ejecutando en realidad. –

+0

@ZoranPavlovic: [proceso de zombie] (http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html) es algo real en POSIX: * Un proceso que ha finalizado y que se elimina cuando su estado de salida ha sido reportado a otro proceso que está esperando que el proceso termine. * – jfs

1

No estoy seguro de lo que quiere decir "Necesito ejecutar mi script con no_wait()", pero creo que este ejemplo hace lo que necesita. Los procesos no serán zombies por mucho tiempo. El proceso principal solo tendrá wait() cuando ya estén finalizados y, por lo tanto, se descomprimirán rápidamente.

#!/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 salida hacia el final se parece a esto:

Still running: [29776, 29774, 29772] 
Still running: [29776, 29774] 
Still running: [29776] 
Still running: [] 
All children terminated 
+0

Esto es excesivo. Como dice Peter: es suficiente para asegurarse de que no está guardando ninguna referencia al objeto Popen y se lo cuidará. Solo necesita usar poll() o wait() si realmente desea esperar a que los procesos secundarios finalicen, lo cual no se menciona en la pregunta. – ibz

+0

Además, si * desea * esperar a que finalicen los procesos hijo, puede usar wait() en cada uno de ellos; no necesita sondear() y eliminarlos de su lista. Hacer una espera() en cada uno asegura que está esperando hasta que el último haya terminado (si eso es lo que desea). – ibz

+0

@ibz: 1. [No es suficiente para evitar mantener referencias a los objetos Popen] (http://stackoverflow.com/questions/2760652/how-to-kill-or-avoid-zombie-processes-with-subprocess- module/2792898 # comment29817645_6236440) 2. [Usando '.wait()' podría crear zombies] (https://gist.github.com/7505372): imagine que ha llamado '.wait()' en un proceso que debería terminar en una hora y tienes otros 1000 niños que deberían salir en un segundo. auge. tienes 1000 zombis en un segundo que no se cosecharán hasta que pase la hora. – jfs

4

El tiempo de ejecución Python asume la responsabilidad de deshacerse de proceso zombie una vez que sus objetos de proceso han sido basura recogida. Si ves al zombi por ahí significa que has guardado un objeto de proceso y no has llamado a esperar, sondear o terminar en él.

+1

[Una simple prueba muestra que no es cierto en Python 2] (https://gist.github.com/zed/7504999). Parece que funciona en Python 3 – jfs

+0

@ J.F.Sebastian ¿Estás seguro de que la prueba es correcta? Como no estás llamando a '.wait()', '.poll()' o '.terminate()', creo que Peter está en lo correcto. – blong

+0

@blong: para llamar a un método, necesita un nombre (o alguna otra forma de referenciar un objeto): la prueba intencionalmente no mantiene referencias explícitas a los objetos 'Popen()'. Comprueba si * "el tiempo de ejecución de python se responsabiliza de deshacerse del proceso zombie una vez que los objetos de proceso han sido recolectados". Hay algo de apoyo para tratar con zombies: 'lista_activo', la función' _cleanup() 'se llama desde' Popen() 'pero la prueba muestra que no ayuda en Python 2. – jfs

3

Si usted utiliza simplemente subprocess.Popen, se le multa - He aquí cómo:

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 __name__ == '__main__': 
    do_some_stuff() 

Usted puede utilizar .poll() en el objeto devuelto por Popen para comprobar si se terminó (sin tener que esperar). Si devuelve None, el hijo aún se está ejecutando.

Asegúrate de no guardar referencias a los objetos de Popen; si lo haces, no serán basura, por lo que terminas con zombies. He aquí un ejemplo:

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 

En el ejemplo anterior, si usted quiere deshacerse de los zombies, puede .wait() en cada uno de los hijos o .poll() hasta que el resultado no es None.

De cualquier manera está bien - o bien no guardando referencias, o usando .wait() o .poll().

+4

[¿Has verificado que tu primera ejemplo no produce zombies si el padre se mantiene con vida?] (http://stackoverflow.com/questions/2760652/how-to-kill-or-avoid-zombie-processes-with-subprocess-module#comment29817645_6236440) – jfs

+0

Awesome ¡responder! Liberar las referencias al proceso asesinado funcionaba como un hechizo. –

15

Si elimina el objeto subproceso, utilizando del para forzar la recolección de basura, que hará que el objeto subproceso que desea borrar y luego los procesos inactivos desaparecerán sin necesidad de terminar su intérprete. Puedes probar esto primero en la interfaz de línea de comando de python.

+0

Esto no funcionó para mí: 's ​​= subprocess.Popen ('exec cat/dev/zero>/dev/null')', luego 'killall cat' luego' del s' → zombie sigue ahí. – MarSoft

+0

Me funcionó. En Celery, cuando se produce la excepción SoftTimeLimitExceeded, llamo 's.kill()' then 'del s'. No zombie – Travis

+0

Esto no funciona en [Python 2] (https://stackoverflow.com/questions/2760652/how-to-kill-or-avoid-zombie-processes-with-subprocess-module#comment29817645_6236440) – darkfeline

0

Recientemente, me encontré con este problema zombie debido a mi secuencia de comandos python. El problema real se debió principalmente a la muerte del subproceso y el proceso principal no sabe que el niño está muerto. Entonces, lo que hice fue simplemente agregar popen.communicate() después de la señal de matar del proceso hijo para que el proceso padre llegue a saber que el hijo está muerto, entonces el kernel actualiza el pid del proceso hijo ya que el hijo ya no está y entonces no hay zombies formados ahora.

PD: la encuesta también es una opción aquí ya que verifica y transmite el estado del hijo al padre. A menudo, en el subproceso, es mejor si usa check_output o call si no necesita comunicarse con stdout y stdin.

Cuestiones relacionadas