python: чтение вывода подпроцесса в потоках

у меня есть исполняемый файл, который я вызываю с помощью подпроцесса.К popen. Затем я намерен передать ему некоторые данные через stdin, используя поток, который считывает его значение из очереди, которая позже будет заполнена в другом потоке. Вывод должен быть прочитан с помощью канала stdout в другом потоке и снова отсортирован в очереди.

насколько я понимаю из моих предыдущих исследований, использование потоков с очередью-хорошая практика.

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

в качестве примера для тестирования исполняемого файла будет просто перетасовать каждую строку (shuffleline.py):

#!/usr/bin/python -u
import sys
from random import shuffle

for line in sys.stdin:
    line = line.strip()

    # shuffle line
    line = list(line)
    shuffle(line)
    line = "".join(line)

    sys.stdout.write("%sn"%(line))
    sys.stdout.flush() # avoid buffers

обратите внимание,что это уже как можно меньше. Или нет? Это моя урезанная тест программа:

#!/usr/bin/python -u
import sys
import Queue
import threading
import subprocess

class WriteThread(threading.Thread):
    def __init__(self, p_in, source_queue):
        threading.Thread.__init__(self)
        self.pipe = p_in
        self.source_queue = source_queue

    def run(self):
        while True:
            source = self.source_queue.get()
            print "writing to process: ", repr(source)
            self.pipe.write(source)
            self.pipe.flush()
            self.source_queue.task_done()

class ReadThread(threading.Thread):
    def __init__(self, p_out, target_queue):
        threading.Thread.__init__(self)
        self.pipe = p_out
        self.target_queue = target_queue

    def run(self):
        while True:
            line = self.pipe.readline() # blocking read
            if line == '':
                break
            print "reader read: ", line.rstrip()
            self.target_queue.put(line)

if __name__ == "__main__":

    cmd = ["python", "-u", "./shuffleline.py"] # unbuffered
    proc = subprocess.Popen(cmd, bufsize=0, stdin=subprocess.PIPE, stdout=subprocess.PIPE)

    source_queue = Queue.Queue()
    target_queue = Queue.Queue()

    writer = WriteThread(proc.stdin, source_queue)
    writer.setDaemon(True)
    writer.start()

    reader = ReadThread(proc.stdout, target_queue)
    reader.setDaemon(True)
    reader.start()

    # populate queue
    for i in range(10):
        source_queue.put("string %sn" %i)
    source_queue.put("")

    print "source_queue empty: ", source_queue.empty()
    print "target_queue empty: ", target_queue.empty()

    import time
    time.sleep(2) # expect some output from reader thread

    source_queue.join() # wait until all items in source_queue are processed
    proc.stdin.close()  # should end the subprocess
    proc.wait()

это дает следующий вывод (python2.7):

writing to process:  'string 0n'
writing to process:  'string 1n'
writing to process:  'string 2n'
writing to process:  'string 3n'
writing to process:  'string 4n'
writing to process:  'string 5n'
writing to process:  'string 6n'
source_queue empty: writing to process:  'string 7n'
writing to process:  'string 8n'
writing to process:  'string 9n'
writing to process:  ''
 True
target_queue empty:  True

затем ничего в течение 2 секунд ...

reader read:  rgsn0i t
reader read:  nrg1sti
reader read:  tis n2rg
reader read:  snt gri3
reader read:  nsri4 tg
reader read:  stir5 gn
reader read:   gnri6ts
reader read:   ngrits7
reader read:  8nsrt ig
reader read:  sg9 nitr

ожидается чередование в начале. Однако выходные данные подпроцесса не отображаются до после подпроцесс заканчивается. С большим количеством строк я получаю некоторый вывод, поэтому я предполагаю проблему кэширования в трубе stdout. Согласно другим вопросам, размещенным здесь, промывка stdout (в подпроцессе) должна работы, по крайней мере на Linux.

1 ответов


ваша проблема не имеет ничего общего с subprocess модуль, или потоки (проблемные, как они есть), или даже смешивание подпроцессов и потоков (a очень плохая идея, даже хуже, чем использование потоков для начала, если вы не используете backport модуля подпроцесса Python 3.2, который вы можете получить от code.google.com/p/python-subprocess32) или доступ к тем же вещам из нескольких потоков (как ваш print заявления делать.)

что происходит, что ваш shuffleline.py программы буферов. Не на выходе, а на входе. Хотя это не очень очевидно, когда вы перебираете fileobject, Python будет читать в блоках, обычно 8K байт. С sys.stdin является fileobject, ваш for цикл будет буферизоваться до EOF или полного блока:

for line in sys.stdin:
    line = line.strip()
    ....

если вы не хотите этого делать, либо используйте цикл while для вызова sys.stdin.readline() (который возвращает '' для EOF):

while True:
    line = sys.stdin.readline()
    if not line:
        break
    line = line.strip()
    ...

или используйте форму двух аргументов iter(), который создает итератор, который вызывает первый аргумент, пока не будет возвращен второй аргумент ("sentinel"):

for line in iter(sys.stdin.readline, ''):
    line = line.strip()
    ...

я также был бы упущен, если бы я не предложил не использовать потоки для этого, а не блокировать ввод-вывод на трубах подпроцесса вместо этого или даже что-то вроде twisted.reactor.spawnProcess который имеет множество способов подключения процессов и других вещей вместе как потребителей и производителей.