2009-10-09 23 views
5

Tengo un problema con el método de Subprocess.Popen de Python.¿Por qué el subproceso.Popen no espera hasta que termina el proceso secundario?

Aquí hay una secuencia de comandos de prueba que demuestra el problema. Se está ejecutando en una caja de Linux.

#!/usr/bin/env python 
import subprocess 
import time 

def run(cmd): 
    p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) 
    return p 

### START MAIN 
# copy some rows from a source table to a destination table 
# note that the destination table is empty when this script is run 
cmd = 'mysql -u ve --skip-column-names --batch --execute="insert into destination (select * from source limit 100000)" test' 
run(cmd) 

# check to see how many rows exist in the destination table 
cmd = 'mysql -u ve --skip-column-names --batch --execute="select count(*) from destination" test' 
process = run(cmd) 
count = (int(process.communicate()[0][:-1])) 

# if subprocess.Popen() waited for the child to terminate than count should be 
# greater than 0 
if count > 0: 
    print "success: " + str(count) 
else: 
    print "failure: " + str(count) 
    time.sleep(5) 

    # find out how many rows exists in the destination table after sleeping 
    process = run(cmd) 
    count = (int(process.communicate()[0][:-1])) 
    print "after sleeping the count is " + str(count) 

Por lo general, la salida de este script es:

success: 100000 

pero a veces es

failure: 0 
after sleeping the count is 100000 

Tenga en cuenta que en el caso de fallo, el selecto inmediatamente después de la inserción muestra 0 filas, pero después Dormir durante 5 segundos por segundo selecciona correctamente muestra un recuento de filas de 100000. Mi conclusión es que uno de los siguientes es verdadero:

  1. subprocess.Popen no está esperando que el hilo hijo a finalizar - Esto parece contradecir la documentación
  2. el inserto de MySQL no es atómico - mi comprensión de MySQL parece indicar inserción es atómica
  3. el selecto no es ver el recuento de filas correcto de inmediato - de acuerdo con un amigo que conoce mysql mejor que yo, esto no debería suceder tampoco

¿Qué me falta?

FYI, soy consciente de que esta es una forma hacky de interactuar con mysql desde Python y MySQLdb probablemente no tenga este problema, pero tengo curiosidad de por qué este método no funciona.

+0

Gracias a todos por las grandes respuestas. Mirando de nuevo la documentación del subproceso, veo que el comentario "Espere a que termine el comando" aparece en las secciones de métodos de conveniencia, no en la sección del método de Popen. Asentí con la cabeza a la respuesta de Jed ya que respondió mejor a mi pregunta original, aunque creo que usaré la solución de Paul para mis futuras necesidades de scripting. –

+0

Tenga en cuenta que os.system (a menos que haga algo más con él) devuelve el VALOR DEVUELTO del proceso (generalmente 0 o 1). No dejes que te muerda tampoco. –

Respuesta

20

subprocess.Popen, cuando se crea una instancia, ejecuta el programa. Sin embargo, no lo espera, lo apaga en segundo plano como si hubiera escrito cmd & en un intérprete de comandos. Entonces, en el código anterior, básicamente has definido una condición de carrera: si las inserciones pueden finalizar a tiempo, parecerán normales, pero si no, obtendrás el resultado inesperado. No está esperando su primer run() 'd PID para finalizar, simplemente está devolviendo su instancia Popen y continúa.

no estoy seguro de cómo este comportamiento contradice la documentación, porque hay algunos métodos muy claras sobre Popen que parecen indicar que no se esperaba, como: sin embargo

Popen.wait() 
    Wait for child process to terminate. Set and return returncode attribute. 

estoy de acuerdo, que la documentación de este módulo podría mejorarse.

que esperar a que el programa termine, me gustaría recomendar el uso subprocess 's método de conveniencia, subprocess.call, o el uso de communicate en un objeto Popen (para el caso cuando se necesita la salida estándar). Ya estás haciendo esto para tu segunda llamada.

### START MAIN 
# copy some rows from a source table to a destination table 
# note that the destination table is empty when this script is run 
cmd = 'mysql -u ve --skip-column-names --batch --execute="insert into destination (select * from source limit 100000)" test' 
subprocess.call(cmd) 

# check to see how many rows exist in the destination table 
cmd = 'mysql -u ve --skip-column-names --batch --execute="select count(*) from destination" test' 
process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) 
try: count = (int(process.communicate()[0][:-1])) 
except: count = 0 

Además, en la mayoría de los casos, no es necesario para ejecutar el comando en una cáscara. Este es uno de esos casos, pero tendrá que volver a escribir su comando como una secuencia.Hacerlo de esa manera también le permite evitar la inyección de shell tradicional y preocuparse menos por citar, así:

prog = ["mysql", "-u", "ve", "--execute", 'insert into foo values ("snargle", 2)'] 
subprocess.call(prog) 

Esto incluso trabajar, y no inyectará como era de esperar:

prog = ["printf", "%s", "<", "/etc/passwd"] 
subprocess.call(prog) 

Pruébalo de forma interactiva. Evita las posibilidades de inyección de proyectil, especialmente si acepta la entrada del usuario. Sospecho que estás utilizando el método de cadena menos impresionante de comunicación con subproceso porque te topaste con problemas para que las secuencias funcionen: ^)

+1

Estoy usando subprocess.call y tampoco parece estar esperando. La declaración enseguida le dice al código que elimine el archivo que acaba de ejecutar, y se llama antes de que se pueda ejecutar el código, bloqueando el programa. – Elliot

4

Amigo, ¿por qué crees que subprocess.Popen devolvió un objeto con un método wait, a menos que fue porque la espera fue NO implícita, intrínseca, inmediata e inevitable, como parece suponer ...?! La razón más común para engendrar un subproceso es NO esperar inmediatamente a que termine, sino dejar que proceda (por ejemplo, en otro núcleo o, en el peor, por división en el tiempo - ese es el sistema operativo - y el hardware - vigía) al mismo tiempo que el proceso principal continúa; cuando el proceso padre necesita esperar a que finalice el subproceso, obviamente llamará al wait en el objeto devuelto por la llamada original subprocess.Process.

7

Si no necesita usar subprocess y popen, generalmente es más fácil usar os.system. Por ejemplo, las secuencias de comandos rápidos hago a menudo algo como esto:

import os 
run = os.system #convenience alias 
result = run('mysql -u ve --execute="select * from wherever" test') 

A diferencia de popen, os.system QUÉ esperar a que su proceso para volver antes de pasar a la siguiente etapa de la secuencia de comandos.

Más información sobre el mismo en la documentación: http://docs.python.org/library/os.html#os.system

Cuestiones relacionadas