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
который имеет множество способов подключения процессов и других вещей вместе как потребителей и производителей.