2009-10-09 2 views
5

У меня проблема с подпроцессом Python.Popen.Почему подпроцесс.Popen не ждет завершения дочернего процесса?

Вот тестовый скрипт, который демонстрирует проблему. Он запускается в ящике 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) 

Обычно выход из этого сценария:

success: 100000 

но иногда это

failure: 0 
after sleeping the count is 100000 

Обратите внимание, что в случае отказа, то выберите сразу после вставки показывает 0 строк, но после спать в течение 5 секунд, второй выбор правильно показывает количество строк в 100000. Я пришел к выводу, что одно из следующего верно:

  1. subprocess.Popen не ждет ребенка нити прекратить - Это, кажется, противоречит в документации по
  2. для MySQL вставки не является атомарной - мое понимание MySQL, кажется, указывает вставка атомное
  3. избранных не является видя правильное количество строк сразу же - по словам друга, который знает mysql лучше, чем я, это не должно происходить ни

Что мне не хватает?

FYI, я знаю, что это хакерский способ взаимодействия с mysql из Python и MySQLdb, скорее всего, не будет иметь этой проблемы, но мне любопытно, почему этот метод не работает.

+0

Спасибо всем за отличные ответы. Еще раз взглянув на документацию подпроцесса, я вижу, что я был отброшен комментарием «Дождитесь завершения команды», которое появляется в разделах методов удобства, а не в разделе метода Popen. Я дал кивком ответ Джеда, так как он наилучшим образом ответил на мой первоначальный вопрос, хотя я думаю, что буду использовать решение Павла для моих будущих потребностей в написании сценариев. –

+0

Имейте в виду, что os.system (если вы не делаете что-то еще с ней) возвращает RETURN VALUE процесса (обычно 0 или 1). Не позволяйте этому укусить вас. –

ответ

20

subprocess.Popen, при создании экземпляра, запускает программу. Однако он не ждет его - он загорается в фоновом режиме, как если бы вы набрали cmd & в оболочке. Итак, в приведенном выше коде вы по существу определили условие гонки - если вставки могут закончить вовремя, это будет нормально, но если вы не получите неожиданный результат. Вы не дожидаетесь своего первого run() 'd PID, чтобы закончить, вы просто возвращаете свой экземпляр Popen и продолжаете.

Я не знаю, как это поведение противоречит документации, потому что есть некоторые очень четкие методы Popen, которые, кажется, показывают, что не ждали, как:

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

Я согласен, однако, что документация для этого модуля может быть улучшена.

Ждать программы, чтобы закончить, я бы рекомендовал использовать subprocess «s удобный метод, subprocess.call, или с помощью communicate на Popen объекта (за исключением случая, когда вам нужно стандартный вывод). Вы уже делаете это для своего второго звонка.

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

Кроме того, в большинстве случаев вам не нужно запускать команду в оболочке. Это один из тех случаев, но вам придется переписать свою команду как последовательность.Делая это таким образом, также позволяет избежать традиционной инъекции оболочки и меньше беспокоиться о цитировании, например, так:

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

Это будет работать даже, и не будет вводить как можно было ожидать:

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

Попробуйте в интерактивном режиме. Вы избегаете возможности инъекции оболочки, особенно если вы принимаете ввод пользователя. Я подозреваю, что вы используете менее классный метод связи с подпроцессом, потому что у вас возникли проблемы с получением последовательности для работы: ^)

+1

Я использую subprocess.call, и он также, кажется, не ждет. Заявление сразу же сообщает, что код удаляет только что запущенный файл, и он вызывается перед тем, как код может быть запущен, сбой программы. – Elliot

4

Чувак, почему вы думаете, что subprocess.Popen возвратил объект с помощью метода wait, за исключением случаев, когда он потому что ожидание было NOT неявное, внутреннее, немедленное и неизбежное, как вы, кажется, догадываетесь ...?! Наиболее распространенной причиной появления подпроцесса является НЕ немедленно дождаться его завершения, а скорее позволить ему продолжить (например, на другом ядре или, в худшем случае, нарезке по времени - это операционная система - и аппаратное обеспечение - lookout) одновременно с продолжением родительского процесса; когда родительский процесс должен дождаться завершения подпроцесса, он, очевидно, вызовет wait на объект, возвращенный исходным вызовом subprocess.Process.

7

Если вам не нужно использовать подпроцесс и всплывающие окна, обычно проще использовать os.system. Например, для быстрых сценариев я часто делаю что-то вроде этого:

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

В отличии от POPEN, os.system НЕ ждать вашего процесс возврата, прежде чем перейти к следующему этапу вашего сценария.

Более подробная информация о нем в документации: http://docs.python.org/library/os.html#os.system

Смежные вопросы