Программа Python с использованием os.pipe и os.fork()

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

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

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

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

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

Ответ 1

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

В примере кода, связанного с этим, это нормально - родитель действует блокировочно и просто использует ребенка для целей изоляции. Если вы хотите продолжить, используйте либо неблокирующий IO, как в коде, который вы отправили (но будьте готовы к полузаполнению данных), либо прочитайте фрагменты (например, r.read(размер) или 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)

Ответ 2

Используя

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

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

Ответ 3

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

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

Ответ 5

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

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

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

Вилка и exec могут произойти где-то там, но вам не нужно заботиться.