select() 在 python2 和 python3 上的行为是否不同?
Does select() behave differently on python2 and python3?
我想从 this 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
,如 posted link:
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 它 returns 总是在有可用数据时恢复,但在这种情况下只有一行将每 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);
}}
似乎原因是在 subprocess.PIPE
中缓冲,第一个 readline()
调用读取所有可用数据(即两行)和 returns 第一个。
之后管道中没有未读数据,所以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)。请参阅此注释:
The BufferedIOBase methods signatures are mostly identical to that of RawIOBase (exceptions: write() returns None , read() 's argument is optional), but may have different semantics. In particular, BufferedIOBase implementations may read more data than requested or delay writing data using buffers.
我想从 this 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
,如 posted link:
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 它 returns 总是在有可用数据时恢复,但在这种情况下只有一行将每 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);
}}
似乎原因是在 subprocess.PIPE
中缓冲,第一个 readline()
调用读取所有可用数据(即两行)和 returns 第一个。
之后管道中没有未读数据,所以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)。请参阅此注释:
The BufferedIOBase methods signatures are mostly identical to that of RawIOBase (exceptions: write() returns None , read() 's argument is optional), but may have different semantics. In particular, BufferedIOBase implementations may read more data than requested or delay writing data using buffers.