Правильный способ повторного использования и закрытия объекта подпроцесса - PullRequest
4 голосов
/ 06 марта 2012

У меня есть следующий код в цикле:

while true:
    # Define shell_command
    p1 = Popen(shell_command, shell=shell_type, stdout=PIPE, stderr=PIPE, preexec_fn=os.setsid)
    result = p1.stdout.read(); 
    # Define condition
    if condition:
       break;

, где shell_command - это что-то вроде ls (просто печатает материал).

Я читал в разных местах, что я могу закрыть / завершить / выйти из объекта Popen различными способами, например:

p1.stdout.close()
p1.stdin.close()
p1.terminate
p1.kill

Мой вопрос:

  1. Чтоявляется правильным способом закрытия объекта subprocess, как только мы его используем?
  2. Учитывая природу моего скрипта, есть ли способ открыть объект subprocess только один раз и повторно использовать его с другими командами оболочки?Будет ли это эффективнее, чем открывать новые subprocess объекты каждый раз?

Обновление

Я все еще немного сбит с толку относительно последовательности шагов, которые необходимо выполнить в зависимости от того,Я использую p1.communicate() или p1.stdout.read() для взаимодействия с моим процессом.

Из того, что я понял в ответах и ​​комментариях:

Если я использую p1.communicate(), мне не нужнобеспокоиться о высвобождении ресурсов, поскольку communicate() будет ожидать завершения процесса, возьмет выходные данные и правильно закроет объект subprocess

Если я пойду по маршруту p1.stdout.read() (который, я думаю, соответствует моей ситуации,так как команда оболочки просто должна печатать вещи) я должен называть вещи в следующем порядке:

  1. p1.wait()
  2. p1.stdout.read()
  3. p1.terminate()

Это верно?

Ответы [ 4 ]

9 голосов
/ 06 марта 2012

Как правильно закрыть объект подпроцесса, как только мы его закончили?

stdout.close() и stdin.close() не завершат процесс , если не завершится самостоятельно в конце ввода или при ошибках записи.

.terminate() и .kill() оба выполняют свою работу, при этом kill немного более "радикально" в системах POSIX при отправке SIGKILL, что не может быть проигнорировано приложением. Конкретные различия объяснены в этом сообщении в блоге , например. В Windows нет разницы.

Кроме того, не забудьте .wait() и закрыть трубы после завершения процесса, чтобы избежать зомби и форсировать освобождение ресурсов.

Особый случай, который часто встречается, это процессы, которые читают из STDIN и записывают свой результат в STDOUT, закрывая себя при обнаружении EOF. С такими программами часто целесообразно использовать subprocess.communicate:

>>> p = Popen(["sort"], stdin=PIPE, stdout=PIPE)
>>> p.communicate("4\n3\n1")
('1\n3\n4\n', None)
>>> p.returncode
0

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

>>> p = Popen(["ls", "/home/niklas/test"], stdin=PIPE, stdout=PIPE)
>>> p.communicate()
('file1\nfile2\n', None)
>>> p.returncode
0

Учитывая природу моего скрипта, есть ли способ открыть объект подпроцесса только один раз и повторно использовать его с другими командами оболочки? Будет ли это эффективнее, чем каждый раз открывать новые объекты подпроцесса?

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

2 голосов
/ 06 марта 2012

Учитывая природу моего скрипта, есть ли способ открыть объект подпроцесса только один раз и повторно использовать его с другими командами оболочки?

Да.

#!/usr/bin/env python
from __future__ import print_function
import uuid
import random
from subprocess import Popen, PIPE, STDOUT

MARKER = str(uuid.uuid4())

shell_command = 'echo a'
p = Popen('sh', stdin=PIPE, stdout=PIPE, stderr=STDOUT,
          universal_newlines=True) # decode output as utf-8, newline is '\n'
while True:
    # write next command
    print(shell_command, file=p.stdin)
    # insert MARKER into stdout to separate output from different shell_command
    print("echo '%s'" % MARKER, file=p.stdin)
    # read command output
    for line in iter(p.stdout.readline, MARKER+'\n'):
        if line.endswith(MARKER+'\n'):
            print(line[:-len(MARKER)-1])
            break # command output ended without a newline
        print(line, end='')
    # exit on condition
    if random.random() < 0.1:
        break
# cleanup
p.stdout.close()
if p.stderr:
   p.stderr.close()
p.stdin.close()
p.wait()

Поместите while True внутрь try: ... finally:, чтобы выполнить очистку в случае исключений. В Python 3.2+ вы можете использовать with Popen(...): вместо.

Будет ли это эффективнее, чем каждый раз открывать новые объекты подпроцесса?

Это имеет значение в вашем случае? Не угадай Измерь это.

2 голосов
/ 06 марта 2012

«Правильный» заказ:

  1. Создать поток для чтения stdout (и второй поток для чтения stderr, если вы не объединили их в один).

  2. Записать команды, которые будут выполняться ребенком, в stdin. Если вы не читаете стандартный вывод одновременно, запись в стандартный ввод может блокироваться.

  3. Закрыть стандартный ввод (это сигнал для ребенка, что теперь он может завершить сам, когда это будет сделано)

  4. Когда stdout возвращает EOF, дочерний процесс завершается. Обратите внимание, что вам нужно синхронизировать поток чтения stdout и ваш основной поток.

  5. позвоните wait(), чтобы узнать, возникла ли проблема, и очистить дочерний процесс

Если вам необходимо остановить дочерний процесс по какой-либо причине (возможно, пользователь захочет выйти), тогда вы можете:

  1. Закрыть стандартный ввод, если ребенок завершает чтение, когда читает EOF.

  2. Убить с помощью terminate(). Это правильное решение для дочерних процессов, которые игнорируют стандартный ввод.

  3. Если ребенок не отвечает, попробуйте kill()

Во всех трех случаях вы должны позвонить wait(), чтобы очистить мертвый дочерний процесс.

1 голос
/ 06 марта 2012
  1. Зависит от того, что вы ожидаете от процесса;Вы всегда должны звонить p1.wait(), чтобы избежать зомби.Другие шаги зависят от поведения подпроцесса;если он выдаст какой-либо вывод, вы должны использовать вывод (например, p1.read() ... но это потребовало бы много памяти) и только потом вызывать p1.wait();или вы можете подождать некоторое время и вызвать p1.terminate(), чтобы завершить процесс, если считаете, что он не работает должным образом, и, возможно, позвонить p1.wait(), чтобы очистить зомби.

В качестве альтернативы p1.communicate(...) выполнит обработку, если io и ждет вас (не убийство).

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