Python等待直到数据输入到sys.stdin

18

我的问题如下:

我的Python脚本通过sys.stdin接收数据,但它需要等待直到有新的数据可用于sys.stdin。

如Python的manpage中所述,我使用了以下代码,但它完全过载了我的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 使用率。


3
听起来你的问题不在这里。在Python脚本中,stdin中是否有数据并不重要,只要它是打开状态。无论是什么正在向Python脚本写入数据,都会过早地关闭流。 - Dunes
首先,您应该了解 readline()readlines() 之间的区别。readlines() 将读取来自 stdin 的所有输入直到 EOF(基本上是调用 read() 然后根据换行符进行分割)。这意味着当 stdin 关闭时,它将第一次返回。对 stdin 进行未来的 readlines() (或 read()readline())调用将返回 [] (或 ""),建议阅读: https://docs.python.org/2/tutorial/inputoutput.html https://unix.stackexchange.com/questions/103885/piping-data-to-a-processs-stdin-without-causing-eof-afterward - FluxLemur
9个回答

22

下面的代码应该可以直接使用。

import sys
for line in sys.stdin:
    # whatever

解释:

代码将迭代stdin中的行,因为它们是随时进入的。如果流仍然打开,但没有完整的行,则循环将挂起,直到遇到换行符(并返回整行)或流被关闭(并返回缓冲区中剩余的内容)。

一旦流被关闭,就不能再向stdin写入或读取任何数据。这是绝对的。

你的代码导致CPU过载的原因是,一旦stdin被关闭,任何后续尝试迭代stdin的尝试都会立即返回而不做任何事情。实际上,你的代码等价于以下内容。

for line in sys.stdin:
    # do something

while 1:
    pass # infinite loop, very CPU intensive
也许你可以分享一下你是如何向stdin写入数据的,这会很有用。
编辑:在Python中,当遇到EOF字符时,它将(针对for循环、迭代器和readlines()方法)认为流已关闭。你可以要求Python读取更多数据,但你不能再使用之前的方法。Python手册建议使用:
import sys
while True:
    line = sys.stdin.readline()
    # do something with line
当遇到EOF字符时,readline将返回一个空字符串。如果流仍然打开,下一次调用readline将像平常一样运行。你可以在终端中运行该命令来自行测试。按下Ctrl+D将导致终端将EOF字符写入stdin。这将导致本帖子中的第一个程序终止,但最后一个程序将继续读取数据,直到流实际关闭。最后一个程序不应该100%占用CPU,因为readline会等待有数据才返回,而不是返回空字符串。
当我尝试从实际文件中读取时,我只遇到了繁忙循环的问题。但是在从stdin读取时,readline会愉快地阻塞。

是的,我也考虑过使用普通日志并使用open打开它,然后使用seek和tell来仅获取其中的新行,但是在Perl中可以工作。因此,我想知道如何在Python中实现这一点。 - Abalus
1
当程序读取输入时,不存在所谓的“EOF字符”。操作系统会拦截^D并关闭程序的标准输入,但程序永远看不到^D。为了验证这一点,请在提示符下键入“cat | wc”,然后立即键入^D:您将向wc发送0个字符。 - alexis
虽然你说得没错,程序缓冲区中没有字面上的EOF字符,但你错误地断言了流已关闭。在输入Ctrl-D后,下一次对read(C的read)的底层实现的调用将返回EOF宏。随后的读取调用将阻塞,直到更多数据进入缓冲区。因此,流从未真正关闭,它只是告诉程序,另一端已经发出停止发送数据的信号。 - Dunes
@estani 如果你感兴趣的话,可以看一下我的新解决方案。https://dev59.com/Jmw05IYBdhLWcg3wv0V6#26640086 - Dunes
@Dunes 看起来不错,但我还在使用 Python 2.7,不能测试它。你能否使用一些流式套接字或其他可以在 sigterming 进程之前接近的东西进行测试? - estani
显示剩余4条评论

4

这实际上是无缺陷的(即没有CPU失控)- 当你从shell中调用脚本时,就像这样:

tail -f input-file | yourscript.py

显然,这不是理想的情况 - 因为你必须将所有相关的stdout写入该文件 - 但是它可以不带太多开销地工作!这主要是因为使用了readline() - 我认为:

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

它实际上会停下来等待输入,直到获取更多输入为止。

希望这能帮助有所需要的人!


3
请使用以下内容:
#!/usr/bin/python
import sys
for line in sys.stdin.readlines():
    pass # do something useful

如果我使用你的代码,脚本将在没有数据剩余时结束。但是我的脚本需要等待新数据到来。 - Abalus
2
不会。for循环将一直挂起等待更多数据。当标准输入被关闭时,循环将结束并且脚本将继续执行。 - hamstergene
不行,这在我的情况下行不通。我的脚本需要像Perl中的这段代码一样运行:#!/usr/bin/perl $| = 1; while (<STDIN>) { # ...在此处放置任何转换或查找... print $_; } - Abalus
1
如果有问题,请尝试这个解决方案并回来。 - hamstergene
实际上,在这种情况下,你们两个都是错的。Abalus:当stdin关闭时,脚本并不会在没有数据剩余时结束(尽管它对你也不起作用)。@hamstergene:sys.stdin.readlines()不会在找到行时产生输出,而只有在收到ctrl-d/EOF时才会产生输出。 - Mr. B

3

经过一段时间后,我回到了这个问题。问题似乎在于Apache将CustomLog视为文件——它可以打开、写入、关闭,然后在以后重新打开。这会导致接收进程被告知其输入流已关闭。但是,这并不意味着进程的输入流不能再次被写入,只是正在写入输入流的任何进程不会再次写入。

处理这个问题的最好方法是设置一个处理程序,并让操作系统知道在写入标准输入时调用处理程序。通常应该避免过度依赖操作系统信号事件处理,因为它们相对昂贵。但是,在以下情况下仅复制1MB文本仅产生了两个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

好的,我现在会坚持这些代码。

#!/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秒内解析一行,而apache2的性能真的很差。

非常感谢您的回答。

最好的问候, Abalus


1
我遇到了一个类似的问题,即Python在循环开始之前等待发送方(无论是用户还是另一个程序)关闭流。我已经解决了这个问题,但显然非常不符合Pythonic,因为我不得不诉诸于while True:sys.stdin.readline()
最终,在另一个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
我知道我在重提旧事,但这似乎是该主题的热门搜索之一。Abalus所采用的解决方案已经固定了每个周期的time.sleep,无论stdin是否为空且程序是否应处于空闲状态或者有很多行等待处理。进行小修改即可使程序快速处理所有消息,并仅在队列实际为空时等待。因此,在睡眠期间到达的只有一行需要等待,其他行都可以在没有任何滞后的情况下被处理。

这个例子只是简单地颠倒输入行,如果您仅提交一行,则会在一秒内(或设置的睡眠周期)做出响应,但也可以快速处理类似“ls -l | reverse.py”这样的内容。即使在像OpenWRT这样的嵌入式系统上,这种方法的CPU负载也是最小的。

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] 即可将 stdin 回显,而不是反转的 stdin。 - Blairg23

0

这个对我有用,/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上进行测试时,并没有CPU负载,alog.py会存在于内存中,如果你使用ps命令就能看到它。

0

我知道这是一个旧的帖子,但我遇到了同样的问题,并发现这更多地与脚本如何被调用有关,而不是脚本本身的问题。至少在我的情况下,这是debian上“系统shell”的问题(即:/bin/sh链接到什么 - 这是apache用来执行CustomLog管道命令的内容)。更多信息请参见:http://www.spinics.net/lists/dash/msg00675.html

祝好, - 史蒂夫


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接