`fprintf(stdout, ...)` 和 `fprintf(stderr, ...)` 在多线程环境下是否保证不交错输出?

3
假设我有两个线程,它们要么向stderr,要么向stdout打印一些东西(相对较长)。这两个流的函数是线程安全的吗?也就是说,它们永远不会"交错"字符吗?例如,如果我有"Hello, World",我永远不会得到"HHellllo,WorldWorld"或任何其他交错的结果吗?这适用于x86、GCC、Linux > 3.0。

可能是重复的问题:在同时运行的线程中调用printf是否线程安全? - Jens Gustedt
@JensGustedt:我看到了,但是它没有回答stderrstdout是否都是线程安全的问题。我在某个地方读到过stderr不是线程安全的,我需要确认一下。 - Dervin Thunk
可能是stdout thread-safe in C on Linux?的重复问题。 - Kate Gregory
2
@DervinThunk 使用仅限书面语言的沟通方式存在挑战。多年前,我学到了如果人们无法从我的写作中理解我的意思,那么我需要更清晰地表达,而不是因为他们误读而责备他们。如果您希望每个人都知道您的问题很特别且与众不同,请明确说明。您也会得到更好的答案。一个简单的“我知道 xyz,我已经阅读了[链接到其他问题],我正在询问 abc”通常就可以解决问题。 - Kate Gregory
2个回答

6
我看了glibc,每次调用vfprintf都会在流上调用POSIX flockfile (_IO_flockfile) 和 funlockfile (_IO_funlockfile)。
因此,在一次调用中的字符不会与来自另一个线程的调用中的字符交错,因为只有一个线程可以持有stdout或stderr上的锁。
尽管如此,多个线程之间的多次调用顺序是不确定的。

好的,这就是我期望的答案。我同意在不同线程中排序存在问题。有人对这个答案有疑问吗? - Dervin Thunk
2
此外,C2011支持线程,并且对于单个调用跨线程锁定单个I/O通道的等效事项也有说明。然而,标准并未说明如果一个线程在stdout上写入,而另一个线程在stderr上写入会发生什么;由于并发执行,这些输出可能会在屏幕上交错显示。 - Jonathan Leffler
@JonathanLeffler 关于“如果一个线程在标准输出上写入,而另一个线程在标准错误上写入会发生什么”的问题:是否有可能将stdout路由到屏幕1,将stderr路由到屏幕2? - pmor

3

不行。即使在单线程程序中,由于不同的缓冲规则,你也可能会出现交错情况。默认情况下,stdout是按行缓冲的,而stderr是不带缓冲的。您可以通过以下方式使它们都不带缓冲:

   setvbuf(stdout, NULL, _IONBF, 0)

另请参阅Linux下C语言中的线程安全输出stdout?,特别是R.的答案。还有Jonathan Leffler的评论

编辑:默认情况下,标准输出(stdout)将在每行末尾或缓冲区已满时进行fflush操作。后一种情况可能发生在一行的中间。然后,如果stdout和stderr都具有相同的底层文件描述符,则输出fprintf(stderr,...可插入到一行的中间。

但情况可能更糟。通常,您希望将stderr和stdout同时重定向到文件或管道。我的系统上setbuf(3)的手册页面指出:

如果流引用终端(正如stdout通常所做的那样),则它是行缓冲的。标准错误流stderr默认始终是无缓冲的。

因此,在这种情况下,stdout变为缓冲,并且实际上似乎每个输出到stderr都与stdout的输出交织在一起。这可以通过添加以下内容来缓解:

   setlinebuf(stdout);

或者通过使标准输出无缓冲。


1
单线程代码中,你会得到什么交错效果?一个 printf 完成写入后,另一个 printf 才会开始。抱歉,我不理解你的意思。 - Dervin Thunk
3
默认情况下,stdout 会在每行结束时或者缓冲区满时进行 fflush 操作。后一种情况可能会发生在一行的中间。然后,如果 stdout 和 stderr 具有相同的底层文件描述符,则输出 fprintf(stderr,... 可能会插入到一行的中间。 - Joseph Quinsey
请参考Jonathan Leffler在https://dev59.com/P2nWa4cB1Zd3GeqP4tmF#12989477的评论。 - Joseph Quinsey

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