在基于 Windows 的操作系统中,假设有几个不同的进程可以通过使用fopen/fopen_s/fwrite
etc 频繁地读取和/或写入文件,在这种情况下,我是否需要考虑数据竞争,或者操作系统可以自动处理以确保文件只能在任何给定时间由单个进程打开/更新,而其余的 fopen 尝试将失败?那么在这件事上基于 linux 的操作系统呢?
3 回答
在 Windows 中,这取决于您打开文件的方式。
在和的情况下查看uStyle
参数的一些可能值。OpenFile
dwShareMode
CreateFile
请注意,OpenFile
尽管如此更好地使用,但它已被弃用CreateFile
。
您必须注意不要同时从多个线程打开同一个文件 - 因为完全有可能多次打开文件,并且操作系统可能会或可能不会按照您的预期执行,具体取决于您打开文件的模式- 例如,如果您创建一个新文件,它肯定会创建两个不同的文件(其中一个在关闭时会消失,因为它已被另一个线程删除,太好了,嗯?)。规则非常复杂,最糟糕的是,如果你不加倍小心,你会得到“混合输出到同一个文件”——所以行甚至部分行会从两个线程中混合。
即使操作系统阻止您两次打开同一个文件,您仍然必须处理“FILE *
回来为NULL
”的后果。那你怎么办呢?回去再试一次,还是失败,还是?
我不确定我能否就如何解决这个问题提出一个好的建议,因为你没有很好地描述你对这些文件做了什么。有一些不同的事情浮现在脑海中:
- 保留文件名的“寄存器”,并为每个必须保留才能打开文件的文件的互斥锁。
- 使用单个“文件线程”来读取/写入文件上的数据,然后将“我想将这些内容写入文件 aa.txt”排队,并让工作人员随心所欲地写入。
- 使用较低级别的文件系统调用,并使用对文件的“独占”访问,在发生冲突时使用某种“退避”行为。
我敢肯定还有很多其他方法可以解决这个问题——这真的取决于你想要做什么。
也许。如果您谈论的是不同的进程(而不是线程),则适用于线程的传统数据竞争条件不适用。但是(这里的 Unix 和 Windows 没有区别):
任何单个
write
/WriteFile
操作都是原子的。(关于 Windows,我不能 100% 确定,但我无法想象其他情况。)但是,如果您使用 iostream 或较旧的FILE*
功能,则无法直接控制这些操作何时发生。通常,它们只会在流的缓冲区已满时发生。您需要确保缓冲区足够大,并在每次输出后显式刷新。(如果您要输出合理长度的行,例如最多 80 个字符,则可以肯定的是缓冲区将保存完整的行。在这种情况下,只需使用std::endl
iostream 中的行来终止;对于 C 样式函数,您必须setvbuf( stream, NULL, _IOLBF, 0 )
在第一个输出之前调用。进程中的每个打开的文件都有自己的想法,即在文件中写入的位置,以及文件结尾的位置。如果您希望所有写入都转到文件末尾,则需要用
std::ios_base::app
C++ 或 C 中的“a”打开它。仅std::ios_base::out
/"w"
是不够的。(当然,如果只使用std::ios_base::out
或“w”,文件将在打开时被截断。有几个不同的进程截断文件可能会导致数据丢失。)读取其他进程正在写入的文件时:当您到达文件末尾时,流或
FILE
进入错误状态,并且不会尝试进一步读取,即使其他进程正在追加数据。在 C 中,clearerr
应该(我认为)撤消这个,但不清楚接下来会发生什么;在 C++ 中,清除流中的错误并不一定意味着进一步的读取也不会立即遇到文件结尾。在这两种情况下,最安全的选择是记住每次读取之前您在哪里,如果读取失败,请关闭文件,然后重新打开它,寻找您所在的位置,然后从那里开始阅读。随机访问,除了在文件末尾之外的写入,也可以工作,只要所有写入都是原子的(见上文);您应该始终获得一致的状态。但是,如果您所写的内容取决于您所读的内容,并且其他进程正在执行类似的操作,则您将需要文件锁定,这在 iostream/
FILE*
级别不可用。