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

时间:2015-01-16 09:12:48

标签: linux select python-3.x pipe python-2.x

我想从this帖子中描述的同一个帖子中的子流程中读取stdoutstderr。虽然在Python2.7中运行代码按预期工作,但Python3.3中的select()调用似乎不应该如此。

看看 - 这是一个脚本,可以在stdoutstderr上打印两行,然后等待,然后重复几次:

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)

有问题的脚本将在子流程中启动上面的脚本,并按照发布的链接中所述读取stdoutstderr

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不缓冲stdoutstderr。此外,我在调用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);
}}

1 个答案:

答案 0 :(得分:1)

似乎原因是subprocess.PIPE中的缓冲,并且第一个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模块(PEP 31136)。请参阅此说明:

  

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