Потоковое чтение из подпроцесса - PullRequest
0 голосов
/ 29 декабря 2018

Мне нужно прочитать выходные данные дочернего процесса по мере его производства - возможно, не на каждом write, но задолго до завершения процесса.Я пробовал решения из документов Python3 и вопросов SO здесь и здесь , но я все еще ничего не получаю, пока ребенок не завершит работу.

Приложение предназначено для мониторинга обучениямодели глубокого обучения.Мне нужно получить выходные данные теста (около 250 байт для каждой итерации с интервалом примерно в 1 минуту) и следить за статистическими сбоями.

  • Я не могу изменить механизм обучения;например, я не могу вставить stdout.flush() в код дочернего процесса.
  • Я могу разумно дождаться накопления дюжины строк;Я надеялся, что заполнение буфера решит мою проблему.

Код: варианты закомментированы.

Родитель

cmd = ["/usr/bin/python3", "zzz.py"]
# test_proc = subprocess.Popen(
test_proc = subprocess.run(
    cmd,
    stdout=subprocess.PIPE,
    stderr=subprocess.STDOUT
    )

out_data = ""
print(time.time(), "START")
while not "QUIT" in str(out_data):
    out_data = test_proc.stdout
    # out_data, err_data = test_proc.communicate()
    print(time.time(), "MAIN received", out_data)

Ребенок (zzz.py)

from time import sleep
import sys

for _ in range(5):
    print(_, "sleeping", "."*1000)
    # sys.stdout.flush()
    sleep(1)

print("QUIT this exercise")

Несмотря на отправку строк размером более 1000 байт, заполнение буфера (проверенное в другом месте как 2 КБ; здесь я поднялся до 50 КБ) не заставляет родителя «видеть» новый текст.

Чего мне не хватает, чтобы заставить это работать?


Обновление, касающееся ссылок, комментариев и опубликованного ответа iBug:

  • Popen вместо run исправлена ​​проблема блокировки.Каким-то образом я пропустил это в документации и моих экспериментах с обоими.
  • universal_newline=True аккуратно изменил возврат байтов в строку: легче обрабатывать на принимающей стороне, хотя с чередующимися пустыми строками (легко обнаружить и отбросить).
  • Установка bufsize на что-то крошечное (например, 1) ни на что не повлияло;родитель все еще должен ждать, пока ребенок заполнит буфер stdout, в моем случае - 8 000.
  • export PYTHONUNBUFFERED=1, прежде чем выполнение действительно решило проблему буферизации.Спасибо wim за ссылку.

Если кто-то не придумает канонического, изящного решения, которое делает их устаревшими, я приму ответ iBug завтра.

1 Ответ

0 голосов
/ 02 января 2019

subprocess.run всегда порождает дочерний процесс, а блокирует поток до тех пор, пока он не выйдет .

Единственный вариант для вас - использовать p = subprocess.Popen(...) и читать строки с s = p.stdout.readline()или p.stdout.__iter__() (см. ниже).

Этот код работает для меня, если дочерний процесс сбрасывает стандартный вывод после печати строки (расширенную заметку см. ниже).

cmd = ["/usr/bin/python3", "zzz.py"]
test_proc = subprocess.Popen(
    cmd,
    stdout=subprocess.PIPE,
    stderr=subprocess.STDOUT
)

out_data = ""
print(time.time(), "START")
while not "QUIT" in str(out_data):
    out_data = test_proc.stdout.readline()
    print(time.time(), "MAIN received", out_data)
test_proc.communicate()  # shut it down

См. мойжурнал терминала (точки удалены из zzz.py):

ibug@ubuntu:~/t $ python3 p.py
1546450821.9174328 START
1546450821.9793346 MAIN received b'0 sleeping \n'
1546450822.987753 MAIN received b'1 sleeping \n'
1546450823.993136 MAIN received b'2 sleeping \n'
1546450824.997726 MAIN received b'3 sleeping \n'
1546450825.9975247 MAIN received b'4 sleeping \n'
1546450827.0094354 MAIN received b'QUIT this exercise\n'

Вы также можете сделать это с помощью петли for:

for out_data in test_proc.stdout:
    if "QUIT" in str(out_data):
        break
    print(time.time(), "MAIN received", out_data)

Если вы не можете изменить дочерний элементпроцесс, unbuffer (из пакета expect - установка с APT или YUM) может помочь.Это мой рабочий родительский код без изменения дочернего кода.

test_proc = subprocess.Popen(
    ["unbuffer"] + cmd,
    stdout=subprocess.PIPE,
    stderr=subprocess.STDOUT
)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...