Linux select()在python2和python3上的行为是否不同?

Linux select()在python2和python3上的行为是否不同?,linux,select,python-3.x,pipe,python-2.x,Linux,Select,Python 3.x,Pipe,Python 2.x,我想从post中描述的同一线程中的子进程中读取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.st

我想从post中描述的同一线程中的子进程中读取
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-select()中的一个bug吗?我是否误解了
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
.
.
选择
读线标准
标准输出:标准输出:0
读线错误
标准:错误:0

选择原因似乎是在
子流程管道中进行缓冲,第一个
readline()
调用读取所有可用数据(即两行)并返回第一行

之后,管道中没有未读数据,因此
select()
不会立即返回。您可以通过加倍readline调用来检查这一点:

print("stdout: " + p.stdout.readline().decode().strip())
print("stdout: " + p.stdout.readline().decode().strip())
并确保第二个
readline()
调用不会阻塞

一种解决方案是使用
bufsize=0
禁用缓冲:

p = subprocess.Popen(['/usr/bin/env', 'python', '-u', 'test-output.py'],
                 stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0)
另一个可能的解决方案是执行非阻塞
readline()
或询问管道文件对象的读取缓冲区大小,但我不知道这是否可行

您还可以直接从
p.stdout.fileno()
读取,以实现非阻塞
readline()

更新:Python2与Python3

Python3与Python2不同的原因可能在于新的I/O模块()。见本说明:

BuffereDiabase方法签名与RawIOBase方法签名基本相同(例外:write()返回None,read()的参数是可选的),但可能具有不同的语义。特别是,BuffereDiabase实现可能会读取比请求更多的数据,或者使用缓冲区延迟写入数据


您是否尝试过明显的方法:显式地调用
.flush()
-u
在Python3中的行为不同——流是行缓冲的);设置
bufsize=0
;使用
os.read(fd,512)
而不是
.readline()
?代码本身已损坏,例如:即使
p.poll()不是None,也可能存在数据缓冲
另一个变化是sys.stdout/err在3.x中成为io.TextIOWrapper对象,跨平台的行为(和默认值)一致。以前,对于CPython来说,它们是文件对象,它对所使用的特定C编译器的stdio结构进行了精简包装。我建议仔细阅读io模块文档。@J.F.Sebastian:子进程不必是Python程序。同样的C++程序(显式刷新)也会显示同样的效果。另外,
.poll()
也没有影响-您可以将行替换为
,而将其设置为True
。目前我非常确信,
select()
在Python3中被破坏了。@frans:细节很重要。如果按照我的建议使用
os.read(fd,512)
,会发生什么情况?@J.F.Sebastian:bufsize=0
提示(以及
read()
)是正确的,我没有再次检查,因为我已经检查过了,但我想当时我有另一个问题。不幸的是,我无法将此标记为答案,但您的评论值得投票。看起来您是对的,将
bufsize
设置为0(正如J.F.Sebastian所建议的)就可以做到这一点。但是我仍然不明白为什么Python2的行为是不同的。Python2
read()
也被缓冲了吗?@frans,在Python2中,readline()似乎只读取一行并返回它,而在Python3中,它读取所有可用数据并返回第一行。我已经更新了答案。
p = subprocess.Popen(['/usr/bin/env', 'python', '-u', 'test-output.py'],
                 stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0)