Двунаправленный обмен данными с помощью анонимных каналов

dvunapravlennyj obmen dannymi s pomoshhju anonimnyh kanalov Системные инструменты параллельного выполнения

Обычно каналы позволяют данным перемещаться только в одном направлении — один конец является входом, другой выходом. А как быть, если потребуется организовать общение между программами в обоих направлениях? Например, одна программа может посылать другой запрос на информацию и ждать получения этой информации. Один канал не может справиться с такими двунаправленными переговорами, но это можно реализовать с помощью двух каналов: один канал используется для передачи запроса, а второй — для пересылки ответа запросившей программе.

Так происходит во множестве практических применений. Например, однажды я написал графический интерфейс для отладчика командной строки C-подобного языка программирования и связал два процесса каналами, используя описываемый прием. Графический интерфейс запускался как отдельный процесс, который конструировал и отправлял команды отладчику командной строки в его поток ввода через канал, а затем анализировал результаты, возвращаемые отладчиком через его поток вывода. Графический интерфейс выступал в роли программиста, вводящего команды с клавиатуры, и как клиент отладчика-сервера. Вообще говоря, возможность запускать программы командной строки как дочерние процессы, с потоками ввода-вывода, подключенными к каналам, позволяет добавлять новые интерфейсы к старым программам. Простой пример реализации графического интерфейса подобного рода мы увидим в главе 10.

Модуль в примере 5.22 демонстрирует один из способов реализации идеи связывания стандартных потоков ввода и вывода двух программ. В нем функция spawn запускает новую дочернюю программу и соединяет потоки ввода и вывода родительской программы с потоками ввода и вывода дочерней программы. Это означает, что:

     Когда родитель читает из своего стандартного потока ввода, происходит чтение текста, отправленного дочерней программой в свой стандартный поток вывода.

     Когда родитель записывает в свой стандартный поток вывода, происходит отправка данных в стандартный поток ввода дочерней программы.

В итоге две независимые программы обмениваются между собой данными через свои стандартные потоки ввода-вывода.

Пример 5.22. PP4E\System\Processes\pipes.py

запускает дочерний процесс/программу, соединяет свои потоки stdin/stdout

с потоками stdout/stdin дочернего процесса — операции чтения и записи на стороне родительского процесса отображаются на стандартные потоки ввода-вывода дочерней программы; напоминает соединение потоков с помощью модуля subprocess;

import os, sys

def spawn(prog, *args): # имя программы, аргументы командной строки

stdinFd = sys.stdin.fileno() # получить дескрипторы потоков

stdoutFd = sys.stdout.fileno() # обычно stdin=0, stdout=1

подпись: parentstdin, childstdout = os.pipe() childstdin, parentstdout = os.pipe() pid = os.fork()
if pid:
os.close(childstdout) os.close(childstdin) os.dup2(parentstdin, stdinfd) os.dup2(parentstdout, stdoutfd) else:
os.close(parentstdin) os.close(parentstdout) os.dup2(childstdin, stdinfd) os.dup2(childstdout, stdoutfd) args = (prog,) + args os.execvp(prog, args)
assert false, ‘execvp failed!’
подпись: # создать два канала ipc
# pipe возвращает (inputfd, outoutfd)
# создать копию процесса
# в родительском после ветвления:
# закрыть дочерние концы в родителе
# копия sys.stdin = pipe1[0]
# копия sys.stdout = pipe2[1]
# в дочернем после ветвления:
# закрыть родительские концы
# копия sys.stdin = pipe2[0]
# копия sys.stdout = pipe1[1]
# запустить новую программу
# os.exec никогда не вернется сюда

if __name__ == ‘__main__’: mypid = os.getpid() spawn(‘python’, ‘pipestestchild.py’, spam’) # породить дочернюю прогр.

print(‘Hello 1 from parent’, mypid) # в stdin дочерней прогр.

sys.stdout.flush() # вытолкнуть буфер stdio

reply = input() # из потока вывода потомка

sys.stderr.write(‘Parent got: “%s”\n’ % reply) # stderr не связан

# с каналом!

print(‘Hello 2 from parent’, mypid) sys.stdout.flush() reply = sys.stdin.readline() sys.stderr.write(‘Parent got: “%s”\n’ % reply[:-1])

Функция spawn в этом модуле не работает под управлением стандартной версии Python для Windows (не забывайте, что функции fork в этой системе пока нет). В действительности большинство функций, используемых в этом модуле, отображаются непосредственно в системные вызовы Unix (и могут ужаснуть разработчиков, которые не пишут для Unix!). С некоторыми из этих функций мы уже встречались (например, os.fork), но значительная часть этого программного кода основывается на концепциях Unix, разобраться с которыми должным образом в данной книге нам не позволит время. Тем не менее ниже приводится упрощенное описание системных вызовов, использованных в этом примере: os.fork

Создает копию вызывающего процесса и возвращает числовой идентификатор ID дочернего процесса только родительскому процессу.

os.execvp

Затирает вызывающий процесс новой программой. Эта функция очень похожа на использовавшуюся выше функцию os.execlp, но принимает кортеж или список аргументов командной строки (в аргументе *args в заголовке функции).

os.pipe

Возвращает кортеж дескрипторов файлов, представляющих входной и выходной концы канала, как показано в приведенных ранее примерах.

os.close(fd)

Закрывает файл с дескриптором fd.

os.dup2(fd1, fd2)

Копирует всю системную информацию, связанную с файлом, заданным дескриптором fd1, в файл, заданный дескриптором fd2.

Что касается стандартных потоков ввода-вывода, самое важное место здесь занимает функция os.dup2. Например, вызов os.dup2(parentStdin, stdinFd) по сути присваивает дескриптор файла stdin родительского процесса входному концу одного из создаваемых каналов — все операции чтения из потока stdin с этого момента будут извлекать данные из канала. После соединения другого конца этого канала с копией файла потока stdout дочернего процесса посредством os.dup2(childStdout, stdoutFd) текст, выводимый дочерним процессом в его поток sdtdout, будет отправляться через канал в поток stdin родителя. По своему эффекту этот прием напоминает способ, которым мы соединяли потоки ввода-вывода с помощью модуля subprocess в главе 3, но этот сценарий менее переносим и действует на более низком уровне.

Для проверки этой утилиты в конце файла помещен программный код самотестирования, который запускает в дочернем процессе программу, приведенную в примере 5.23, и производит операции чтения и записи в стандартные потоки ввода-вывода, осуществляя обмен данными через два канала.

Пример 5.23. PP4E\System\Processes\pipes-testchild.py

import os, time, sys

mypid = os.getpid()

parentpid = os.getppid()

sys.stderr.write(‘Child %d of %d got arg: “%s”\n’ %

(mypid, parentpid, sys.argv[1]))

for i in range(2):

time.sleep(3) # приостановить родительский процесс

recv = input() # stdin связан с каналом: данные будут поступать из

#  родительского потока вывода stdout

time.sleep(3)

send = ‘Child %d got: [%s]’ % (mypid, recv)

print(send) # stdout связан с каналом: данные будут поступать в

#  родительский поток ввода stdin

sys.stdout.flush() # гарантировать отправку, иначе процесс заблокируется

Ниже приводятся результаты тестирования в Cygwin (напоминает Unixподобные системы, такие как Linux). Вывод не производит большого впечатления, но показывает, как две программы выполняются независимо и обмениваются данными через каналы, управляемые операционной системой. Этот пример еще более напоминает модель клиент/сервер (если представить себе дочерний процесс как сервер, отвечающий на запросы родителя). Текст, заключенный в квадратные скобки, попал из родительского процесса в дочерний и вернулся обратно в родительский, и все это через каналы, подключенные к стандартным потокам ввода- вывода:

[C:\\PP4E\System\Processes]$ python pipes.py

Child 9228 of 9096 got arg: “spam”

Parent got: “Child 9228 got: [Hello 1 from parent 9096]”

Parent got: “Child 9228 got: [Hello 2 from parent 9096]”

Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011

Каталог сайтов Всего.ру
Оцените статью
Секреты программирования
Добавить комментарий