Python ждать, пока данные в sys.stdin - PullRequest
13 голосов
/ 14 августа 2011

Моя проблема заключается в следующем:

Мой скрипт pythons получает данные через sys.stdin, но ему нужно подождать, пока новые данные не будут доступны на sys.stdin.

Как описано в man-странице из python, я использую следующий код, но он полностью перегружает мой процессор.

#!/usr/bin/python -u
import sys
while 1:
     for line in sys.stdin.readlines():
         do something useful

Есть ли хороший способ решить проблему с высокой загрузкой процессора?

Редактировать:

Все ваши решения не работают. Я даю вам именно мою проблему.

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

Это выглядит примерно так:

CustomLog "|/usr/bin/python -u /usr/local/bin/client.py" combined

Apache2 ожидает от моего скрипта, что он всегда запускается, ждет данных на sys.stdin и анализирует их, а затем обнаруживает данные.

Если я использую только цикл for, скрипт завершится, потому что в какой-то момент в sys.stdin нет данных, и apache2 скажет: «О, ваш скрипт неожиданно завершил работу».

Если я использую цикл while true, мой сценарий будет использовать процессор на 100%.

Ответы [ 9 ]

20 голосов
/ 14 августа 2011

Следующее должно просто работать.

import sys
for line in sys.stdin:
    # whatever

Обоснование:

Код будет перебирать строки в stdin по мере их поступления. Если поток все еще открыт, но нетЕсли строка завершена, цикл будет зависать до тех пор, пока либо не встретится символ новой строки (и не будет возвращена вся строка), либо поток не будет закрыт (и все, что осталось в буфере, будет возвращено).

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

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

for line in sys.stdin:
    # do something

while 1:
    pass # infinite loop, very CPU intensive

Может быть, было бы полезно, если бы вы опубликовали, как вы пишете данные в стандартный ввод.

РЕДАКТИРОВАТЬ:

Python (для целей циклов, итераторов и readlines () будет считать поток закрытым, когда он встречает символ EOF. Вы можете попросить python прочитать больше данных после этого, но вы не можете использовать ни один из предыдущихметоды. Страница man python рекомендует использовать

import sys
while True:
    line = sys.stdin.readline()
    # do something with line

При обнаружении символа EOF readline вернет пустую строку. Следующий вызов readline будет функционировать как обычно, если поток все еще открыт. Вы можете проверить этосамостоятельно, запустив команду в терминале. Нажатие Ctrl + D приведет к тому, что терминал запишет символ EOF в stdin. Это приведет к завершению первой программы в этом посте, но последняя программа продолжит читать данные до тех пор, пока потокна самом деле закрыт. Последняя программа не должна 100% ваш процессор, так как readline будет ждать, пока естьчтобы вернуть, а не возвращать пустую строку.

У меня проблема с занятым циклом, только когда я пытаюсь прочитать строку из реального файла.Но при чтении со стандартного ввода readline счастливо блокирует.

2 голосов
/ 21 августа 2015

Это на самом деле работает без нареканий (то есть без запуска процессора) - когда вы вызываете скрипт из оболочки, например так:

tail -f input-file | yourscript.py

Очевидно, что это не идеально - так как вызатем нужно записать все соответствующие stdout в этот файл -

, но он работает без больших накладных расходов!А именно из-за использования readline() - я думаю:

while 1:
        line = sys.stdin.readline()

На самом деле он остановится и будет ждать в этой строке, пока не получит больше ввода.

Надеюсь, это кому-нибудь поможет!

2 голосов
/ 14 августа 2011

Хорошо, теперь я буду придерживаться этих строк кода.

#!/usr/bin/python
import sys
import time
while 1:
    time.sleep(0.01)
    for line in sys.stdin:
        pass # do something useful

Если я не использую time.sleep, скрипт создаст слишком высокую нагрузку на использование процессора.

Если я использую:

for line in sys.stdin.readline():

Он будет анализировать только одну строку за 0,01 секунды, а производительность apache2 очень плохая. Большое спасибо за ваши ответы.1011 *

2 голосов
/ 14 августа 2011

Используйте это:

#!/usr/bin/python
import sys
for line in sys.stdin.readlines():
    pass # do something useful
1 голос
/ 29 октября 2014

Я вернулся к проблеме через долгое время.Похоже, проблема заключается в том, что Apache обрабатывает CustomLog как файл - что-то, что он может открывать, записывать, закрывать, а затем открывать позже.Это приводит к тому, что получающему процессу сообщают, что его входной поток закрыт.Однако это не означает, что входной поток процессов не может быть записан снова, просто тот процесс, который записывал во входной поток, не будет снова записывать в него.

Лучший способ справиться с этим - этонастройте обработчик и дайте знать ОС, чтобы он вызывал каждый раз, когда ввод записывается в стандартный ввод.Обычно следует избегать сильной зависимости от обработки событий ОС, поскольку они относительно дороги.Тем не менее, копирование мегабайта текста в следующее произвело только два события SIGIO, так что в этом случае все нормально.

fancyecho.py

import sys
import os
import signal
import fcntl
import threading

io_event = threading.Event()

# Event handlers should generally be as compact as possible.
# Here all we do is notify the main thread that input has been received.
def handle_io(signal, frame):
    io_event.set()

# invoke handle_io on a SIGIO event
signal.signal(signal.SIGIO, handle_io)
# send io events on stdin (fd 0) to our process 
assert fcntl.fcntl(0, fcntl.F_SETOWN, os.getpid()) == 0
# tell the os to produce SIGIO events when data is written to stdin
assert fcntl.fcntl(0, fcntl.F_SETFL, os.O_ASYNC) == 0

print("pid is:", os.getpid())
while True:
    data = sys.stdin.read()
    io_event.clear()
    print("got:", repr(data))
    io_event.wait()

Как вы можете использовать эту игрушечную программу.Вывод очищен из-за чередования ввода и вывода.

$ echo test | python3 fancyecho.py &
[1] 25487
pid is: 25487
got: 'test\n'
$ echo data > /proc/25487/fd/0
got: 'data\n'
$
1 голос
/ 29 апреля 2014

У меня была похожая проблема, когда python ждет, пока отправитель (пользователь или другая программа) закроет поток, прежде чем цикл начнет выполняться. Я решил это, но это было явно не питоническим, так как мне пришлось прибегнуть к while True: и sys.stdin.readline()

Я в конце концов нашел ссылку в комментарии в другой записи на модуль с именем io , который является альтернативой стандартному объекту файла. В Python 3 это по умолчанию. Из того, что я могу разглядеть, Python 2 рассматривает stdin как обычный файл, а не как поток.

Попробуйте, у меня это сработало:

sys.stdin = io.open(sys.stdin.fileno())  # default is line buffering, good for user input

for line in sys.stdin:
    # Do stuff with line
1 голос
/ 15 марта 2014

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

Этот пример просто переворачивает входные строки, если вы отправляете только одну строку, на которую он отвечает за секунду (или независимо от того, какой период ожидания вы установили), но также можете очень быстро обработать что-то вроде «ls -l | reverse.py» , Загрузка ЦП для такого подхода минимальна даже на встроенных системах, таких как OpenWRT.

import sys
import time

while True:
  line=sys.stdin.readline().rstrip()
  if line:       
    sys.stdout.write(line[::-1]+'\n')
  else:
    sys.stdout.flush()
    time.sleep(1)
0 голосов
/ 10 марта 2012

Это работает для меня, код /tmp/alog.py:

#! /usr/bin/python

import sys

fout = open("/tmp/alog.log", "a")

while True:
    dat = sys.stdin.readline()
    fout.write(dat)
    fout.flush()

в http.conf:

CustomLog "|/tmp/alog.py" combined

Ключ не использовать

for dat in sys.stdin:

Вы будете ждать там, ничего не получите. А для тестирования запомните fout.flush (), иначе вы можете не увидеть вывод. Я тестирую на fedora 15, python 2.7.1, Apache 2.2, а не на процессор

0 голосов
/ 31 января 2012

Я знаю, что это старый поток, но я наткнулся на ту же проблему и понял, что это больше связано с тем, как был вызван сценарий, а не с сценарием.По крайней мере, в моем случае это оказалось проблемой с «системной оболочкой» в debian (то есть: с чем связан / bin / sh - это то, что apache использует для выполнения команды, которую передает CustomLog).Больше информации здесь: http://www.spinics.net/lists/dash/msg00675.html

hth, - Стив

...