如果多个线程同时在相同的FILE*
变量上调用fflush()
,会发生什么不良情况(如未定义行为、文件损坏等)?
澄清:我不是指并发写文件,而是指并发刷新文件。
这些线程不会同时读取或写入文件(它们只在关键区域内一个接一个地写入文件)。他们只会在关键区域外部进行刷新,以便更快释放关键区域,让其他线程做其他工作(除了文件写入)。
尽管可能存在一个线程正在写入文件(在关键区域内),而另一个线程/线程正在刷新文件(在关键区域外)的情况。
如果多个线程同时在相同的FILE*
变量上调用fflush()
,会发生什么不良情况(如未定义行为、文件损坏等)?
澄清:我不是指并发写文件,而是指并发刷新文件。
这些线程不会同时读取或写入文件(它们只在关键区域内一个接一个地写入文件)。他们只会在关键区域外部进行刷新,以便更快释放关键区域,让其他线程做其他工作(除了文件写入)。
尽管可能存在一个线程正在写入文件(在关键区域内),而另一个线程/线程正在刷新文件(在关键区域外)的情况。
C语言中的流(Streams)是线程安全的。在访问流之前,需要锁定该流的函数。
fflush函数是线程安全的,并且只要流是输出流或更新流,就可以在任何线程中调用它。
1 根据当前标准,即C11。
2 (引自:ISO/IEC 9899:201x 7.21.3 Streams 7)
每个流都有一个相关联的锁,用于在多个执行线程访问流时防止数据竞争,并限制多个线程执行的流操作。一次只能有一个线程持有此锁。锁可重入:单个线程可以同时多次持有锁。
3 (引自:ISO/IEC 9899:201x 7.21.3 Streams 8)
所有读取、写入、定位或查询流位置的函数在访问流之前会锁定该流。完成访问后,它们会释放与流关联的锁。
锁可重入:单个线程可以同时多次持有锁。
4 (引自:ISO/IEC 9899:201x 7.21.5.2 The fflush function 2)
如果流指向的是最近一次操作不是输入的输出流或更新流,则fflush函数将导致该流的所有未写数据传递到主机环境以写入文件;否则,行为是未定义的。
fwrite
和fflush
对同一个FILE
进行操作,并没有出现任何问题。写入操作在输出文件中被很好地交错执行。 - rustyx操作字符流(由指向FILE类型对象的指针表示)的POSIX.1和C语言函数有着“必须”实现可重入性的要求(参见ISO/IEC 9945:1-1996,§8.2)。
这个要求有一个缺点;为了实现可重入性,必须在函数实现中构建同步机制,这会带来相当大的性能损失。POSIX.1c通过引入以下C语言标准I/O函数的高性能、非可重入(潜在不安全)版本,在可重入性(安全性)与性能之间进行权衡:getc()、getchar()、putc()和putchar()。非可重入版本的名称是getc_unlocked()等,以突出它们的不安全性。
需要注意的是,许多流行的系统(包括Windows和Android)并不符合POSIX标准。
fflush
不是线程安全的,他们没有实现POSIX.1c标准,还是他们不声称符合该标准? 一个列出各个平台(包括版本信息)在这方面符合性的清单(或链接)可能是一个有用的资源。 - PJTraillfflush
是线程安全的,即使它当前是线程安全的(参见 https://sourceforge.net/u/lluct/me722-cm/ci/86c46fc79a15bc9500fbb47241a15c148b8abb01/tree/bionic/libc/stdio/fflush.c),但是Microsoft至少在Windows上保证线程安全(参见 https://msdn.microsoft.com/en-us/library/9yky46tz.aspx)。 - geocarfflush()
,这会导致未定义的行为,因此我将假设流以写入或更新模式打开。"w+"
或"r+"
)打开,则在调用fflush()
时,最后一个操作不能是读取操作。由于流被异步地用于各种线程,如果你进行任何读取操作,要确保不会读取到不一致的数据,这将需要某种形式的进程间通信和同步机制或锁。仍然有一个有效的原因可以以更新模式打开文件,但请确保在启动fflush
线程后不要再进行任何读取操作。
fflush()
不会修改当前位置,它只会导致任何缓冲输出被写入系统。流通常受锁保护,因此在一个线程中调用fflush()
不应该干扰其他线程执行的输出,但它可能会改变系统写入的时间。如果多个线程输出相同的FILE*
,则交错发生的顺序是不确定的。此外,如果您在不同的线程中为同一流使用fseek()
,则必须使用自己的锁来确保fseek()
和随后的输出之间的一致性。fflush()
。fflush()
不会修改当前位置。 - chqrlie实际答案似乎是流本身(应该)是线程安全的,但如果不是这种情况,您的问题可能是fflush
在(锁定外部)发生,而另一个线程正在写入(在关键部分内)。
因此,我会与您编码的虚拟机模型一起工作,并将fflush()
也放在关键部分中。
FILE
是线程安全的答案,一个负面后果可能是一个线程被迫等待另一个线程刷新,尽管这只有在您需要相当高的性能时才会有影响。使用像flockfile
或flock
这样的函数可能会导致死锁,但在您描述的情况下可能不会发生。 - PJTraillFILE*
中的所有内容都刷新到一致状态(包括第二个线程写入的内容 - 这取决于竞争),然后第二个线程应确保它刚刚写入的内容也被刷新。当然,使用专用线程进行异步刷新有更好的方法,但这更加复杂,因此超出了范围。 - Serge Rogatch