Linux和Windows中标准输出缓冲的差异

8

当在控制台输出stdout时,Windows和Linux的缓冲方式存在差异。考虑以下Python脚本:

import time
for i in xrange(10):
    time.sleep(1)
    print "Working" ,

在Windows上运行此脚本时,我们会看到一个接一个地出现Working,每个之间需要等待一秒钟。在Linux上,我们必须等待10秒钟,然后整行一起出现。
如果我们将最后一行改为print "Working",则在Linux上每一行也会单独出现。
因此,在Linux上,stdout似乎是按行缓冲的,而在Windows上则没有。我们可以通过使用-u选项关闭缓冲(在这种情况下,Linux上的脚本与Windows上的脚本具有相同的行为)。文档中写道:

-u 强制stdin、stdout和stderr完全不带缓冲区。

实际上,它并没有说,没有-u选项时stdinstdout是有缓冲的。因此我的问题如下:
  1. 为什么在Linux/Windows上有不同的行为?
  2. 是否有某种保证,即如果重定向到文件,无论哪个操作系统,stdout都将被缓冲?至少在Windows和Linux上似乎是这样。
我的主要关注点不是(如一些答案所假设的)信息何时被刷新,而是如果stdout没有被缓冲,那么它可能会严重影响性能,而且人们不应该依赖它。 编辑:值得注意的是,对于Python3,Linux和Windows的行为是相等的(但这并不令人惊讶,因为行为是通过print方法的参数显式配置的)。

7
Python 2 使用 C stdio,而 Windows CRT 在 stdout 是 tty(即字符设备)时默认不进行缓冲,而在磁盘文件或管道上则不同。 - Eryk Sun
5个回答

6
假设您在谈论CPython(很可能是这样),这与底层C实现的行为有关。
ISO C标准提到了(C11 7.21.3文件/ 3)三种模式:
无缓冲(字符尽快出现);
完全缓冲(当缓冲区满时才出现字符);和
行缓冲(字符在换行输出时出现)。
还有其他触发器会导致字符出现(例如即使没有输出换行符也会填充缓冲区,某些情况下请求输入或关闭流),但它们对于您的问题来说并不重要。
重要的是同一标准中的7.21.3文件/ 7:
“最初打开时,标准错误流未完全缓冲;仅当可以确定流不引用交互式设备时,标准输入和标准输出流才是完全缓冲的。”
请注意那里的灵活性。标准输出可以是行缓冲或无缓冲,除非实现确定它不是交互式设备。
在这种情况下(控制台),它是一个交互式设备,因此实现不允许使用无缓冲。然而,它允许选择另外两种模式,这就是为什么您会看到差异的原因。
无缓冲输出会在输出时立即出现消息(类似于Windows行为)。行缓冲将延迟到输出换行符(您的Linux行为)。
如果您真的想确保无论模式如何都刷新消息,请自己刷新它们:
import time, sys
for i in xrange(10):
    time.sleep(1)
    print "Working",
    sys.stdout.flush()
print

关于确保在将输出重定向到文件时进行缓冲的问题,这已经在我已经展示的标准引用中有所涉及。如果可以确定流正在使用非交互设备,则会完全缓冲。这并不是一个绝对的保证,因为它没有说明如何确定这一点,但我很惊讶如果任何实现不能弄清楚。

无论如何,您可以通过重定向输出并监视文件以查看它是每个输出刷新一次还是在结束时刷新来测试具体的实现。


4
行为不同是因为缓冲区通常是“未指定”的,这意味着实现可以按照自己的意愿进行,也就是说,它们可能随时更改或以未记录的方式发生变化,甚至在同一平台上也可能如此。
例如,在Linux上打印一个足够长的字符串,没有换行符(\n),它很可能会被写入作为一个换行符(因为它超过了缓冲区)。你还可能发现缓冲区大小在stdout、管道和文件之间有所不同。
依赖未指定的行为是非常糟糕的,因此当你需要写入字节时,请使用flush()函数。
如果你需要控制缓冲区(例如出于性能原因),那么你需要在write()和flush()上实现自己的缓冲区。这很容易做到,并且可以完全控制何时以及如何实际写入字节。

对我来说,实际上这是关于性能的问题:无缓冲的标准输出会导致巨大的性能损失,如果没有保证,我必须使用额外的层来确保缓冲。 - ead
2
不确定而不是未定义。未定义意味着任何事情都可能发生,而不确定则意味着有限数量的事情之一可能会发生。 - paxdiablo

1

Windows和Linux具有非常不同的控制台输出驱动程序。在Linux中,输出会被缓冲,直到您的程序出现\n。

如果您想手动强制刷新缓冲区,请使用

import sys
sys.stdout.flush()

我不太明白你所说的“控制台输出驱动程序”的意思。对于Python3的 print,在Windows和Linux上的行为是相同的:stdout 是行缓冲的。 - ead

1

这个问题已经在其他地方有了答案,但我会在下面进行总结。

  1. Windows和Linux上行为不同的原因是打印命令实现的方式不同(正如eryksun的评论中所指出的)。您可以在这里这里获取更多相关信息。

  2. Python 中有许多方法可以解决这个问题。更多信息请参见这里


1
这个问题自2011年以来就已经被知晓,详见Python bug issue #11633。 print函数不执行任何缓冲。它写入的文件对象可能会进行缓冲,即使sys.stdout也可能如此。为了考虑到行为差异,找到的解决方案是更新文档,加入以下粗体字句子:
文件参数必须是具有write(string)方法的对象;如果不存在或为None,则将使用sys.stdout。输出缓冲由文件确定。
值得注意的是:
Guido表示这就是应该做的,"需要刷新的应用程序应调用flush()。" 因此,代码更改被拒绝。

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