Динамическая связь между основным и подпроцессом в Python - PullRequest
0 голосов
/ 06 января 2019

Я работаю в Python и хочу найти рабочий процесс, позволяющий двум процессам ( основной процесс и подпроцесс ) взаимодействовать друг с другом. Под этим я подразумеваю способность основного процесса отправлять некоторые данные в подпроцесс (возможно, путем записи в подпроцесс stdin) и способность подпроцесса отправлять некоторые данные обратно в основной. Это также подразумевает, что оба могут читать отправленные им данные (я думал о чтении со стандартного ввода).

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

Я прочитал много ответов здесь о StackOverflow, решая проблемы, тесно связанные с моими, но ни одна из них я не нашла удовлетворительной, поскольку вопросы, на которые эти ответы предназначались, отличались от моих в одной важной детали: мне нужна моя основной процесс для возможности обмена данными со своим подпроцессом динамически столько раз, сколько необходимо , а не только один раз, что в Параметр turn подразумевает, что подпроцесс должен работать до тех пор, пока не получит определенную команду от main-process на завершение.

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

Ответы [ 2 ]

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

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

Базовый пример того, как две трубы (они однонаправлены) могут использоваться между двумя процессами:

import os

def child():
    """
    This function is executed in a child process.
    """
    infile = os.fdopen(r1)
    outfile = os.fdopen(w2, 'w', buffering=1)
    for line in infile:
        if line.rstrip() == 'quit':
            break
        outfile.write(line.upper())

def parent():
    """
    This function is executed in a parent process.
    """
    outfile = os.fdopen(w1, 'w', buffering=1)
    infile = os.fdopen(r2)
    print('Foo', file=outfile)
    print(infile.readline(), end='')
    print('bar', file=outfile)
    print(infile.readline(), end='')
    print('quit', file=outfile)

(r1, w1) = os.pipe()  # for parent -> child writes
(r2, w2) = os.pipe()  # for child -> parent writes

pid = os.fork()
if pid == 0:
    child()  # child code runs here.
elif pid > 0:
    parent()  # parent code runs here.
    os.waitpid(pid, 0)  # wait for child
else:
    raise RuntimeError("This should not have happened.")
# Once returned from corresponding function, both processes exit

Действительно, было бы проще и практичнее использовать subprocess, и вы, вероятно, захотите exec другой двоичный файл / файл. Прежнему нужно было бы сказать, чтобы он не закрывал (по крайней мере, соответствующий канал) файловые дескрипторы, второму требовалось бы, чтобы дескрипторы файлов канала были наследуемыми (не установлен флаг O_CLOEXEC). В остальном то же, что и выше.

Код ребенка:

import os
import sys

infile = os.fdopen(int(sys.argv[1]))
outfile = os.fdopen(int(sys.argv[2]), 'w', buffering=1)

for line in infile:
    if line.rstrip() == 'quit':
        break
    outfile.write(line.upper())

Родительский скрипт:

import os
import subprocess

(r1, w1) = os.pipe2(0)  # for parent -> child writes
(r2, w2) = os.pipe2(0)  # for child -> parent writes

child = subprocess.Popen(['./child.py', str(r1), str(w2)], pass_fds=(r1, w2))
outfile = os.fdopen(w1, 'w', buffering=1)
infile = os.fdopen(r2)
print('Foo', file=outfile)
print(infile.readline(), end='')
print('bar', file=outfile)
print(infile.readline(), end='')
print('quit', file=outfile)
child.wait()

Если подумать об этом, я забыл спросить, нужен ли ребенку стандартный ввод / вывод для чего-либо, или его можно было бы использовать для ввода / вывода информации. Это было бы еще проще:

Ребенок:

import sys

for line in sys.stdin:
    if line.rstrip() == 'quit':
        break
    print(line.upper(), end='', flush=True)

Родитель:

import os
import subprocess

(r1, w1) = os.pipe2(0)  # for parent -> child writes
(r2, w2) = os.pipe2(0)  # for child -> parent writes

child = subprocess.Popen(['./child.py'], stdin=r1, stdout=w2)
outfile = os.fdopen(w1, 'w', buffering=1)
infile = os.fdopen(r2)
print('Foo', file=outfile)
print(infile.readline(), end='')
print('bar', file=outfile)
print(infile.readline(), end='')
print('quit', file=outfile)
child.wait()

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

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

Вы хотите создать Popen объект с subprocess.PIPE для стандартного ввода и вывода и использовать его файловые объекты для связи - вместо того, чтобы использовать одну из колготок , как run (и более старые более конкретные, такие как check_output). Задача состоит в том, чтобы избежать тупика : легко попасть в ситуацию, когда каждый процесс пытается написать, буферы канала заполняются (потому что никто не читает из них), и все зависает. Вы также должны помнить flush в обоих процессах, чтобы избежать зависания запроса или ответа в буфере объекта file.

Popen.communicate предоставляется, чтобы избежать этих проблем, но он поддерживает только одну строку (вместо текущего разговора). Традиционное решение - select, но оно также работает с использованием отдельных потоков для отправки запросов и чтения результатов. (Это одна из причин использовать потоки CPython, несмотря на GIL: каждый существует для запуска, в то время как другой заблокирован, поэтому очень мало споров.) Конечно, тогда возникает проблема синхронизация , и вам может потребоваться проделать некоторую работу, чтобы заставить многопоточный клиент действовать как простой синхронный вызов функции извне.

Обратите внимание, что обоим процессам необходимо flush, но этого достаточно, если либо реализует такой неблокирующий ввод / вывод; один обычно выполняет эту работу в процессе, который запускает другой, потому что именно там он, как известно, необходим (и такие программы являются исключением).

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