Программа Python, использующая проблему os.pipe и os.fork()

Недавно мне нужно было написать скрипт, который выполняет os.fork() для разделения на два процесса. Дочерний процесс становится серверным процессом и передает данные обратно родительскому процессу, используя канал, созданный с помощью os.pipe(). Дочерний элемент закрывает 'r' конец канала, а родитель закрывает 'w' конец канала, как обычно. Я конвертирую возврат из pipe() в файловые объекты с помощью os.fdopen.

Проблема, с которой я сталкиваюсь, заключается в следующем: процесс успешно разветвляется, и ребенок становится сервером. Все отлично работает, и ребенок добросовестно записывает данные в открытый 'w' конец канала. К сожалению, родительский конец канала делает две странные вещи:
А) Он блокирует операцию read() на 'r' конце канала.
Во-вторых, ему не удается прочитать какие-либо данные, которые были помещены в канал, если только 'w' конец полностью закрыт.

Я сразу подумал, что проблема в буферизации, и добавил вызовы pipe.flush(), но это не помогло.

Может ли кто-нибудь пролить свет на то, почему данные не появляются до тех пор, пока конец записи не будет полностью закрыт? И есть ли способ сделать вызов read() неблокирующим?

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


person Paradox    schedule 16.05.2009    source источник
comment
Почему вы не используете модуль подпроцесса?   -  person S.Lott    schedule 16.05.2009
comment
Я думал, что модуль подпроцесса предназначен для вызова команды. Я разветвляюсь с двумя ветвями кода, одной для ребенка и одной для родителя.   -  person Paradox    schedule 16.05.2009
comment
С модулем подпроцесса можно делать практически все что угодно. Старая версия работает нормально, но она очень похожа на UNIX. Подпроцесс немного более четко сопоставлен с Windows.   -  person Charlie Martin    schedule 16.05.2009
comment
Я посмотрю на использование подпроцесса в будущем. Я нашел решение, которое работает для моей стратегии fork(). Спасибо!   -  person Paradox    schedule 16.05.2009


Ответы (4)


Вы используете read() без указания размера или рассматриваете канал как итератор (for line in f)? Если это так, то это, вероятно, источник вашей проблемы - read() определен для чтения до конца файла перед возвратом, а не просто для чтения того, что доступно для чтения. Это будет означать, что он будет заблокирован до тех пор, пока ребенок не вызовет close().

В приведенном примере кода это нормально — родитель действует блокирующим образом и просто использует дочерний элемент в целях изоляции. Если вы хотите продолжить, то либо используйте неблокирующий ввод-вывод, как в коде, который вы разместили (но будьте готовы иметь дело с полуполными данными), либо читайте кусками (например, r.read(size) или r.readline() ), который будет блокироваться только до тех пор, пока не будет прочитан определенный размер/строка. (вам все равно нужно вызвать флеш для ребенка)

Похоже, что обработка канала как итератора также использует дополнительный буфер, поскольку «for line in r:» может не дать вам того, что вы хотите, если вам нужно, чтобы каждая строка была немедленно использована. Возможно, это можно отключить, но просто указать 0 для размера буфера в fdopen кажется недостаточным.

Вот пример кода, который должен работать:

import os, sys, time

r,w=os.pipe()
r,w=os.fdopen(r,'r',0), os.fdopen(w,'w',0)

pid = os.fork()
if pid:          # Parent
    w.close()
    while 1:
        data=r.readline()
        if not data: break
        print "parent read: " + data.strip()
else:           # Child
    r.close()
    for i in range(10):
        print >>w, "line %s" % i
        w.flush()
        time.sleep(1)
person Brian    schedule 16.05.2009
comment
Ницца! также рассмотрите возможность замены while 1: ... на for data in iter(r.readline,""): - person mdaoust; 03.07.2016

С использованием

fcntl.fcntl(readPipe, fcntl.F_SETFL, os.O_NONBLOCK)

Перед вызовом read() решил обе проблемы. Вызов read() больше не блокируется, и данные появляются после сброса() в конце записи.

person Paradox    schedule 16.05.2009

Я вижу, вы решили проблему блокировки ввода-вывода и буферизации.

Примечание, если вы решите попробовать другой подход: subprocess является эквивалентом/заменой идиомы fork/exec. Кажется, что это не то, что вы делаете: у вас есть только вилка (не exec) и обмен данными между двумя процессами - в этом случае multiprocessing (в Python 2.6+) подойдет лучше.

person dF.    schedule 16.05.2009
comment
Этот модуль выглядит очень интересно. Спасибо, я проверю. - person Paradox; 16.05.2009
comment
+1 за упоминание разницы между fork() (что здесь пытается сделать ОП) и идиомой fork/exec, инкапсулированной модулем подпроцесса, что является чем-то совершенно другим. - person Daniel Pryden; 26.06.2010

«Родительская» и «дочерняя» часть fork в приложении Python глупы. Это наследие 16-битных дней Unix. Это притворство того дня, когда fork/exec и exec были важными вещами, чтобы максимально использовать крошечный процессор.

Разбейте свой код Python на две отдельные части: родительскую и дочернюю.

Родительская часть должна использовать подпроцесс для запуска дочерней части.

Где-то там может произойти fork и exec, но вам не нужно об этом заботиться.

person S.Lott    schedule 16.05.2009
comment
Родительский и дочерний элемент является частью основной семантики запуска подпроцесса. Один является подпроцессом, а другой нет. - person Charlie Martin; 16.05.2009
comment
Несмотря на то, что вилка создает родительский и дочерний процессы, это не обязательно для создания подпроцесса. Open VMS так не работает. Модуль подпроцесса намного проще, чем этот форк. - person S.Lott; 16.05.2009
comment
Не думайте, что fork() является эквивалентом CreateProcess() в Windows или эквивалентом в VMS, по которому в основном моделируется модуль подпроцесса. fork() гораздо больше похоже на запуск нового потока, за исключением того, что поток имеет другое пространство процесса (и поэтому вам нужно взаимодействовать с ним через каналы, а не через общую память). При использовании модуля subprocess вам необходимо выполнить инициализацию процесса (например, анализ файлов конфигурации или аргументов командной строки) дважды, а при использовании модуля fork() этого не требуется. Таким образом, fork() может быть намного эффективнее. - person Daniel Pryden; 26.06.2010