【发布时间】:2015-01-16 09:12:47
【问题描述】:
我想从this 帖子中描述的同一线程中的子进程中读取stdout 和stderr。虽然在 Python2.7 中运行代码按预期工作,但 Python3.3 中的 select() 调用似乎没有达到应有的效果。
看看 - 这是一个脚本,它会在 stdout 和 stderr 上打印两行,然后等待,然后重复几次:
import time, sys
for i in range(5):
sys.stdout.write("std: %d\n" % i)
sys.stdout.write("std: %d\n" % i)
sys.stderr.write("err: %d\n" % i)
sys.stderr.write("err: %d\n" % i)
time.sleep(2)
有问题的脚本将在子进程中启动上面的脚本并读取它的 stdout 和 stderr,如发布的链接中所述:
import subprocess
import select
p = subprocess.Popen(['/usr/bin/env', 'python', '-u', 'test-output.py'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
r = [p.stdout.fileno(), p.stderr.fileno()]
while p.poll() is None:
print("select")
ret = select.select(r, [], [])
for fd in ret[0]:
if fd == p.stdout.fileno():
print("readline std")
print("stdout: " + p.stdout.readline().decode().strip())
if fd == p.stderr.fileno():
print("readline err")
print("stderr: " + p.stderr.readline().decode().strip())
请注意,我使用 -u 选项启动 Python 子进程,这会导致 Python 不缓冲 stdout 和 stderr。此外,我在调用 select() 和 readline() 之前打印一些文本以查看脚本阻塞的位置。
问题出在这里:在 Python3 中运行脚本,每次循环后输出阻塞 2 秒,尽管事实上还有两行等待读取。正如每次调用 select() 之前的文本所示,您可以看到阻塞的是 select()(而不是 readline())。
我的第一个想法是select() 仅在 Python3 上刷新时恢复,而 Python2 总是在有可用数据时返回,但在这种情况下,每 2 秒只会读取一行(事实并非如此!)
所以我的问题是:这是 Python3-select() 中的错误吗?我误解了select() 的行为吗?有没有办法在不必为每个管道启动线程的情况下解决此问题?
运行 Python3 时的输出:
select
readline std
stdout: std: 0
readline err
stderr: err: 0
select <--- here the script blocks for 2 seconds
readline std
stdout: std: 0
select
readline std
stdout: std: 1
readline err
stderr: err: 0
select <--- here the script should block (but doesn't)
readline err
stderr: err: 1
select <--- here the script blocks for 2 seconds
readline std
stdout: std: 1
readline err
stderr: err: 1
select <--- here the script should block (but doesn't)
readline std
stdout: std: 2
readline err
stderr: err: 2
select
.
.
编辑:请注意,子进程是否为 Python 脚本没有影响。下面的C++程序也有同样的效果:
int main() {
for (int i = 0; i < 4; ++i) {
std::cout << "out: " << i << std::endl;
std::cout << "out: " << i << std::endl;
std::cerr << "err: " << i << std::endl;
std::cerr << "err: " << i << std::endl;
fflush(stdout);
fflush(stderr);
usleep(2000000);
}}
【问题讨论】:
-
您是否尝试过显而易见的事情:显式调用
.flush()(-u在 Python 3 中的行为不同——流是行缓冲的);设置bufsize=0;使用os.read(fd, 512)而不是.readline()?代码本身已损坏,例如:即使p.poll() is not None也可能有数据缓冲 -
另一个变化是 sys.stdout/err 在 3.x 中变成了 io.TextIOWrapper 对象,具有跨平台的一致行为(和默认值)。以前,对于 CPython,它们是文件对象,对使用的特定 C 编译器的 stdio 结构进行了薄薄的包装。我建议仔细阅读 io 模块文档。
-
@J.F.Sebastian:子进程不必是 Python 程序。执行相同操作(并显式刷新)的 C++ 程序将显示相同的效果。
.poll()也没有影响 - 您可以用while True替换该行。目前我非常确信select()在 Python3 中被破坏了。 -
@frans:细节很重要。如果你按照我的建议使用
os.read(fd, 512)会发生什么? -
@J.F.Sebastian: 你对
bufsize=0的提示是对的(还有read())我没有再次检查它,因为我已经这样做了,但我想我还有另一个问题那时候。不幸的是,我不能将此标记为答案,但您的 cmets 值得投票。
标签: linux select python-3.x pipe python-2.x