在Linux上,使用printf
向标准输出(stdout)写入内容是否线程安全?使用底层的write
命令呢?
在Linux上,使用printf
向标准输出(stdout)写入内容是否线程安全?使用底层的write
命令呢?
在C标准中并没有明确规定这一点,因为它取决于您所使用的C标准库实现。实际上,C标准甚至没有提及线程,因为某些系统(例如嵌入式系统)不支持多线程。
在GNU实现中(glibc
),大多数与FILE*
对象有关的高级stdio函数是线程安全的。那些不安全的函数通常在它们的名称中加有unlocked
(例如getc_unlocked(3)
)。但是,线程安全性在每个函数调用级别上:例如,如果您对printf(3)
进行多次调用,则保证每个调用都会原子输出,但是其他线程可能会在您调用printf()
之间打印东西。如果您想确保一个I/O调用序列被原子输出,您可以使用一对flockfile(3)/funlockfile(3)
调用来锁定FILE
句柄。请注意,这些函数是可重入的,因此您可以在它们之间安全地调用printf()
,而且不会导致死锁,即使printf()
本身调用了flockfile()
。
诸如write(2)
之类的低级I/O调用应该是线程安全的,但我不能完全确定 - write()
会向内核发出系统调用以执行I/O。这样做的方式取决于您正在使用的内核。它可能是sysenter
指令,也可能是旧系统上的int
(中断)指令。一旦进入内核,内核就要确保I/O是线程安全的。在我刚刚对Darwin Kernel Version 8.11.1进行的测试中,write(2)
似乎是线程安全的。
stdio
函数使用锁定,因此,如果您同时从多个线程使用 printf
,您的程序将不会崩溃、损坏 FILE
对象状态等。但是,所有 stdio
操作都在重复调用 fgetc
和 fputc
的术语中正式指定,因此不存在较大规模的原子性保证。换句话说,如果线程 1 和 2 尝试同时打印 "Hello\n"
和 "Goodbye\n"
,则不能保证输出是 "Hello\nGoodbye\n"
或 "Goodbye\nHello\n"
。它可能是 "HGelolodboy\ne\n"
。实际上,大多数实现将为整个高级写入调用获取单个锁,仅因为这样更有效率,但是您的程序不应假设如此。可能存在一些角落情况未执行此操作;例如,实现可以完全在无缓冲流上省略锁定。stdio
操作是原子的,但保证隐藏在 flockfile
的文档中:http://pubs.opengroup.org/onlinepubs/9699919799/functions/flockfile.html
您可以使用所有引用 (FILE *) 对象的函数应该表现得好像它们在内部使用 flockfile() 和 funlockfile() 来获取这些 (FILE *) 对象的所有权。
flockfile
、ftrylockfile
和 funlockfile
函数自己实现大于单个函数调用的原子写入。从线程安全的角度来看,它们都能够保证当多个线程在同一个文件描述符上调用它们时,应用程序不会崩溃。但是,如果没有进行一些应用层面的锁定,那么写入的内容可能会交错。
我一开始并不清楚是否保证了原子性(即没有交错1),因为标准中讲到的是“限制”交错,而不是“防止”,这与数据竞争的强制要求有所不同。
我倾向于认为它是得到了保证的。标准中讲到的是“限制”交错,因为某些不改变结果的交错仍然是允许的;例如,fwrite
写入一些字节,fseek
回到更多位置,然后再次fwrite
直到原始偏移量,以便两个fwrite
在一起。实现可以重新排序这两个fwrite
并将它们合并成一个写操作。
它是线程安全的;printf 应该是可重入的,您不会在程序中导致任何奇怪或损坏的情况。
您无法保证来自一个线程的输出不会从另一个线程的输出中间开始。如果您关心这一点,您需要开发自己的锁定输出代码以防止多次访问。
printf
不是可重入的,请参见https://dev59.com/QG865IYBdhLWcg3wLrlE。 - Yu Hao