2009-10-09 13 views
5

J'ai un problème avec la méthode subprocess.Popen de Python.Pourquoi le sous-processus.Popen n'attend pas jusqu'à ce que le processus fils se termine?

Voici un script de test qui illustre le problème. Il est exécuté sur une machine 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) 

Habituellement, la sortie de ce script est:

success: 100000 

mais parfois il est

failure: 0 
after sleeping the count is 100000 

Notez que dans le cas d'échec, la sélection immédiatement après l'insertion montre 0 lignes, mais après dormir pendant 5 secondes une seconde sélection correctement affiche un nombre de lignes de 100000. Ma conclusion est que l'un des suivants est vrai:

  1. subprocess.Popen n'attend pas le fil de l'enfant de mettre fin - Cela semble contredire la documentation
  2. l'insert mysql n'est pas atomique - ma compréhension de MySQL semble indiquer insert est atomique
  3. select est pas voir le nombre de lignes correct tout de suite - selon un ami qui sait mysql mieux que je fais cela ne devrait pas se produire non plus

Qu'est-ce qui me manque? Je suis conscient que c'est une manière hacky d'interagir avec mysql de Python et MySQLdb n'aurait probablement pas ce problème, mais je suis curieux de savoir pourquoi cette méthode ne fonctionne pas.

+0

Merci à tous pour les grandes réponses. En regardant à nouveau la documentation du sous-processus, je vois que le commentaire "Attendre la fin de la commande" apparaît dans les sections des méthodes de commodité et non dans la section de la méthode Popen. J'ai donné le signe de la tête à la réponse de Jed, car elle répondait mieux à ma question originale, mais je pense que je vais utiliser la solution de Paul pour mes futurs besoins de script. –

+0

Gardez à l'esprit que os.system (à moins que vous ne fassiez autre chose avec) renvoie la VALEUR RENVOYÉE du processus (généralement 0 ou 1). Ne laissez pas cela vous mordre non plus. –

Répondre

20

subprocess.Popen, lorsqu'il est instancié, exécute le programme. Cependant, il ne l'attend pas - il se déclenche en arrière-plan comme si vous aviez tapé cmd & dans un shell. Donc, dans le code ci-dessus, vous avez essentiellement défini une condition de concurrence - si les insertions peuvent finir à temps, cela semblera normal, mais sinon vous obtiendrez la sortie inattendue. Vous n'attendez pas que votre premier PID run() se termine, vous renvoyez simplement son instance Popen et continuez. Cependant,

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

Je suis d'accord, que:

Je ne sais pas comment ce comportement contredit la documentation, parce qu'il ya des méthodes très claires sur Popen qui semblent indiquer qu'il n'est pas attendu, comme la documentation de ce module pourrait être améliorée.

Pour attendre le programme pour terminer, je vous recommande d'utiliser la méthode de la commodité de subprocess, subprocess.call, ou en utilisant communicate sur un objet Popen (pour le cas où vous avez besoin stdout). Vous faites déjà cela pour votre deuxième appel.

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

De plus, dans la plupart des cas, vous n'avez pas besoin d'exécuter la commande dans un shell. C'est l'un de ces cas, mais vous devrez réécrire votre commande comme une séquence.Le faire de cette façon vous permet également d'éviter l'injection de la coque traditionnelle et du soucis de citer, comme ceci:

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

Cela même travailler et n'injectera que vous attendez:

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

Essayez-le de manière interactive. Vous évitez les possibilités d'injection de shell, en particulier si vous acceptez les entrées de l'utilisateur. Je soupçonne que vous utilisez la méthode de chaîne moins impressionnante de communiquer avec le sous-processus parce que vous a rencontré des difficultés d'obtenir les séquences de travail: ^)

+1

J'utilise subprocess.call et il ne semble pas non plus attendre. L'instruction juste après indique au code de supprimer le fichier qu'il vient de lancer, et il est appelé avant que le code puisse être exécuté, écrasant le programme. – Elliot

4

Mec, pourquoi avez-vous pensé subprocess.Popen retourné un objet avec une méthode wait, à moins qu'il était parce que l'attente était pas implicite, intrinsèque, immédiate et inévitable, comme vous semblez le conjecturer ...?! La raison la plus fréquente d'engendrer un sous-processus est de ne PAS attendre immédiatement la fin, mais plutôt de le laisser continuer (par exemple sur un autre noyau, ou au pire en découpant le temps - c'est le guet du système d'exploitation.) en même temps que le processus parent se poursuit; Lorsque le processus parent doit attendre que le sous-processus soit terminé, il appellera évidemment wait sur l'objet renvoyé par l'appel subprocess.Process d'origine.

7

Si vous n'avez absolument pas besoin d'utiliser des sous-processus et des fenêtres, il est généralement plus simple d'utiliser os.system. Par exemple, pour les scripts rapides que je fais souvent quelque chose comme ceci:

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

Contrairement popen, os.system n'attend pour votre processus pour revenir avant de passer à la prochaine étape de votre script.

Plus d'informations sur dans les docs: http://docs.python.org/library/os.html#os.system

Questions connexes