web-dev-qa-db-ja.com

Pythonデータがsys.stdinに入るまで待つ

私の問題は次のとおりです。

私のpythonsスクリプトはsys.stdinを介してデータを受信しますが、sys.stdinで新しいデータが利用可能になるまで待機する必要があります。

Pythonのマンページで説明されているように、次のコードを使用しますが、CPUが完全に過負荷になります。

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

CPU使用率が高いことを解決する良い方法はありますか?

編集:

すべてのソリューションが機能するわけではありません。私はあなたに正確に私の問題を与えます。

すべてのログラインをプログラムに送信し、ログファイルに書き込まないようにApache2デーモンを構成できます。

これは次のようになります。

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

Apache2は、私のスクリプトから常に実行されることを期待し、sys.stdinでデータを待機して解析し、データが存在します。

Forループのみを使用すると、スクリプトが終了します。これは、ある時点でsys.stdinにデータがなく、Apache2がスクリプトが予期せず終了したと言うためです。

While trueループを使用すると、スクリプトは100%のCPU使用率を使用します。

14
Abalus

以下はうまくいくはずです。

import sys
for line in sys.stdin:
    # whatever

理論的根拠:

コードは、入力時にstdinの行を繰り返し処理します。ストリームがまだ開いているが、完全な行がない場合は、改行文字が検出される(および行全体が返される)か、ストリームが返されるまでループがハングします。が閉じられます(そして、バッファに残っているものはすべて返されます)。

ストリームが閉じられると、stdinへのデータの書き込みまたはstdinからの読み取りはできなくなります。限目。

コードがCPUに過負荷をかけている理由は、stdinが閉じられると、その後stdinを反復しようとすると、何もせずにすぐに戻るためです。本質的に、あなたのコードは以下と同等でした。

for line in sys.stdin:
    # do something

while 1:
    pass # infinite loop, very CPU intensive

Stdinにデータを書き込む方法を投稿すると便利かもしれません。

編集:

Pythonは(forループ、イテレータ、readlines()の目的で、ストリームがEOF文字に遭遇したときに閉じられたと見なします。pythonこの後、より多くのデータがありますが、前の方法を使用することはできません。python manページで使用することをお勧めします

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

EOF文字が検出されると、readlineは空の文字列を返します。ストリームがまだ開いている場合、readlineへの次の呼び出しは通常どおり機能します。コマンドを実行して自分でテストできます。 ctrl + Dを押すと、端末はEOF文字をstdinに書き込みます。これにより、この投稿の最初のプログラムは終了しますが、最後のプログラムは、ストリームは実際には閉じられています。readlineは空の文字列を返すのではなく、返されるデータがあるまで待機するため、最後のプログラムはCPUを100%使用しないでください。

実際のファイルからreadlineを実行しようとすると、ビジーループの問題が発生するだけです。しかし、stdinから読み取る場合、readlineは問題なくブロックします。

20
Dunes

これは実際には問題なく機能します(つまり、CPUが暴走することはありません)-シェルからスクリプトを呼び出すと、次のようになります。

_tail -f input-file | yourscript.py_

明らかに、それは理想的ではありません-その場合、関連するすべてのstdoutをそのファイルに書き込む必要があるためです-

しかし、それは多くのオーバーヘッドなしで動作します!つまり、readline()を使用しているためです。

_while 1:
        line = sys.stdin.readline()
_

実際には停止し、入力が増えるまでその行で待機します。

これが誰かを助けることを願っています!

2
rm-vanda

これを使って:

#!/usr/bin/python
import sys
for line in sys.stdin.readlines():
    pass # do something useful
2
hamstergene

さて、私は今これらのコード行に固執します。

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

Time.sleepを使用しない場合、スクリプトはCPU使用率に高すぎる負荷をかけます。

私が使用する場合:

for line in sys.stdin.readline():

0.01秒で1行しか解析されず、Apache2のパフォーマンスは本当に悪いです。ご回答ありがとうございます。

よろしくアバルス

2
Abalus

古いものを生き生きとさせていることは知っていますが、これはこのトピックのトップヒットの1つであるようです。 Abalusが解決した解決策は、各サイクルでtime.sleepを固定し、stdinが実際に空で、プログラムがアイドリング状態であるか、処理を待機している行が多いかどうかを考慮します。小さな変更により、プログラムはすべてのメッセージを迅速に処理し、キューが実際に空の場合にのみ待機します。したがって、スリープ期間中に到着した1つの回線のみが待機でき、他の回線は遅延なしで処理されます。

この例は、入力行を逆にするだけです。1行だけを送信すると、1秒で応答します(またはスリープ期間が設定されている場合)が、「ls -l | reverse.py」のようなものを非常に迅速に処理することもできます。このようなアプローチのCPU負荷は、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)
1
Robert Špendl

python送信者(ユーザーまたは別のプログラム)がストリームを閉じるのを待ってからループの実行を開始するという同様の問題が発生しています。解決しましたが、明らかにそうではありませんでした。 _while True:_とsys.stdin.readline()に頼らなければならなかったのでPythonic

最終的に、別の post のコメントで、標準のファイルオブジェクトの代替である 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
NZJourneyMan

久しぶりに問題に戻ってきました。問題は、ApacheがCustomLogをファイルのように扱うことであるように思われます。これは、開いて、書き込み、閉じて、後日再度開くことができるものです。これにより、受信プロセスは、入力ストリームが閉じられたことが通知されます。ただし、これは、プロセスの入力ストリームに再度書き込むことができないことを意味するのではなく、入力ストリームに書き込んでいたプロセスが再び書き込むことはないということです。

これに対処する最善の方法は、ハンドラーをセットアップし、入力が標準入力に書き込まれるたびにハンドラーを呼び出すようにOSに通知することです。通常、OSシグナルイベントの処理は比較的高価であるため、これらに大きく依存することは避けてください。ただし、メガバイトのテキストをフォローにコピーすると、生成されたSIGIOイベントは2つだけなので、この場合は問題ありません。

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
Dunes

これが古いスレッドであることは知っていますが、同じ問題に遭遇し、これはスクリプトの問題ではなく、スクリプトの呼び出し方法に関係していることがわかりました。少なくとも私の場合、これはdebianの「システムシェル」の問題であることが判明しました(つまり、/ bin/shがリンクされているもの-これは、ApacheがCustomLogがパイプするコマンドを実行するために使用するものです)。詳細はこちら: http://www.spinics.net/lists/dash/msg00675.html

hth、-スティーブ

0
lonetwin

これは私にとってはうまくいきます、コード/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()を覚えておいてください。そうしないと、出力が表示されない場合があります。私はFedora15でテストします、python 2.7.1、Apache 2.2、CPUロードではありません。alog.pyはメモリに存在します。psで確認できます。

0
PasteBT