Почему subprocess.Popen не ожидает завершения дочернего процесса? - PullRequest
5 голосов
/ 09 октября 2009

У меня проблема с методом Python subprocess.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 лучше, чем я, это также не должно произойти

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

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

Ответы [ 3 ]

21 голосов
/ 09 октября 2009

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

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

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

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

Чтобы дождаться завершения программы, я бы порекомендовал использовать удобный метод subprocess, 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)

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

7 голосов
/ 09 октября 2009

Если вам абсолютно не нужно использовать подпроцесс и всплывающее окно, обычно проще использовать 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

3 голосов
/ 09 октября 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...