2009-10-09 12 views
5

Mam problem z metodą podprocesową.Popen Pythona.Dlaczego podprocesPopen nie czeka, aż proces potomny zostanie zakończony?

Oto skrypt testowy, który demonstruje problem. Jest uruchamiany na Linuksie.

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

Zazwyczaj wyjście z tego skryptu jest:

success: 100000 

ale czasami jest to

failure: 0 
after sleeping the count is 100000 

Należy pamiętać, że w przypadku awarii, wybierz natychmiast po wkładce pokazuje 0 wierszy, ale po spanie przez 5 sekund na sekundę wybierz poprawnie pokazuje liczbę wierszy 100000. Mój wniosek jest taki, że jedna z poniższych jest prawdziwa:

  1. subprocess.Popen nie czeka na wątku potomnego - Wydaje się to sprzeczne z dokumentacją
  2. wkładka mysql nie jest atomowy - moje rozumienie mysql wydaje się wskazywać Wkładka jest atomowy
  3. select nie jest zobaczenie właściwego wiersza od razu - według znajomego, który zna mysql lepiej niż ja to robię, nie powinno się zdarzyć ani jedno, ani drugie:

Czego mi brakuje?

FYI, jestem świadomy, że jest to hackowaty sposób interakcji z mysql z Pythona, a MySQLdb prawdopodobnie nie będzie miał tego problemu, ale jestem ciekaw, dlaczego ta metoda nie działa.

+0

Dzięki wszystkim za wielkie odpowiedzi. Patrząc ponownie na dokumentację podprocesu, widzę, że zostałem zignorowany komentarzem "Czekaj na polecenie do wypełnienia", który pojawia się w sekcjach metod dogodności, a nie w sekcji Metody Popena. Dałem ukłon w stronę odpowiedzi Jeda, ponieważ najlepiej odpowiadało na moje pierwotne pytanie, chociaż myślę, że wykorzystam rozwiązanie Paula do moich przyszłych potrzeb w zakresie pisania skryptów. –

+0

Należy pamiętać, że os.system (chyba że zrobisz coś innego) zwróci WARTOŚĆ ZWRACANA procesu (zwykle 0 lub 1). Nie pozwól, żeby cię to ugryzło. –

Odpowiedz

20

subprocess.Popen, po utworzeniu instancji uruchamia program. Nie czeka na to - wystrzeliwuje go w tle, jakbyś wpuścił w powłokę cmd &. Tak więc, w powyższym kodzie, zasadniczo zdefiniowałeś warunki wyścigu - jeśli wkładki mogą się kończyć w czasie, będzie to wyglądało normalnie, ale jeśli nie otrzymasz nieoczekiwanego wyniku. Nie czekasz na zakończenie pierwszego procesu PID, dlatego po prostu wracasz do instancji Popen i kontynuujesz.

Nie jestem pewien, jak to zachowanie jest sprzeczne z dokumentacją, bo jest kilka bardzo jasnych metod na POPEN które wydają się wskazywać, że nie czekał, jak:

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

zgadzam się jednak, że dokumentacja tego modułu mogłaby zostać ulepszona.

czekać na zakończenie programu, polecam korzystania subprocess „s metody wygodę, subprocess.call lub przy użyciu communicate na Popen obiektu (dla przypadku, gdy trzeba stdout). Robisz to już podczas drugiego połączenia.

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

Ponadto w większości przypadków nie trzeba uruchamiać polecenia w powłoce. To jeden z tych przypadków, ale będziesz musiał przepisać swoje polecenie jak sekwencję.Robi to w ten sposób pozwala również uniknąć tradycyjnego zastrzyk Shell i mniej martwić cytowanie, tak:

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

To będzie pracować, a nie wstrzyknie jak można się spodziewać:

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

Wypróbuj go interaktywnie. Unikniesz możliwości wstrzykiwania powłoki, szczególnie jeśli akceptujesz dane wprowadzone przez użytkownika. Podejrzewam, że używasz mniej niesamowite metody ciąg komunikacji z podproces bo wpadł kłopoty coraz sekwencje pracy: ^)

+1

Używam subprocess.call i również nie wydaje się czekać. Instrukcja zaraz po niej mówi kodowi o usunięciu pliku, który właśnie uruchomił, i jest wywoływana przed uruchomieniem kodu, powodując awarię programu. – Elliot

4

Dude, dlaczego uważasz subprocess.Popen zwrócony obiekt o sposobie wait, chyba że było spowodowane tym, że oczekiwanie było ukryte, nieodłączne, natychmiastowe i nieuniknione, jak się wydaje zakładać, że ...?! Najczęstszym powodem odradzania podprocesu NIE jest natychmiastowe oczekiwanie na jego zakończenie, ale raczej pozwolenie na kontynuowanie (np. Na innym rdzeniu lub, co najgorsze, na podstawie podziału czasu) - to system operacyjny - i sprzęt - punkt obserwacyjny) w tym samym czasie, w którym proces nadrzędny trwa; gdy proces nadrzędny musi czekać na zakończenie podprocesu, oczywiście wywoła on wait na obiekcie zwróconym przez oryginalne wywołanie subprocess.Process.

7

Jeśli nie musisz używać podprocesu i popenu, zwykle łatwiej jest użyć os.system. Na przykład w przypadku szybkich skryptów ja często zrobić coś takiego:

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

przeciwieństwie popen, os.system będzie czekał na swój proces powrotu przed przejściem do następnego etapu skryptu.

Więcej informacji na jej temat w docs: http://docs.python.org/library/os.html#os.system

Powiązane problemy