一般来说,当我们从多个进程附加到UNIX中的文件时,我们可以理所当然地认为什么?是否有可能丢失数据(一个进程会覆盖其他进程)?数据是否可能被破坏? (例如,每个进程在每个追加到日志文件时附加一行,是否有可能两行被破坏?)如果追加在上述意义上不是原子的,那么确保互斥的最佳方法是什么?
答案 0 :(得分:56)
大小为'PIPE_BUF'的写入应该是原子的。这至少应该是512字节,虽然它可能很容易变大(linux似乎将其设置为4096)。
这假设您正在谈论所有完全符合POSIX标准的组件。例如,在NFS上不是这样。
但假设您写入以“O_APPEND”模式打开的日志文件并将您的行(包括换行符)保留在“PIPE_BUF”字节长的情况下,您应该能够将多个写入器写入日志文件而不会出现任何损坏问题。任何中断都将在写入之前或之后到达,而不是在中间。如果您希望文件完整性在重新启动后继续存在,您还需要在每次写入后调用fsync(2)
,但这对于性能而言非常糟糕。
澄清:阅读评论和Oz Solomon's answer。我不确定O_APPEND
是否应该具有PIPE_BUF
大小的原子性。它完全有可能是Linux实现write()
的方式,或者可能是由于底层文件系统的块大小。
答案 1 :(得分:30)
修改:2017年8月更新了最新的Windows结果。
我将给出一个答案,其中包含测试代码和结果的链接,作为提议的Boost.AFIO的作者,它实现了异步文件系统和文件i / o C ++库。
首先,Windows上的O_APPEND或等效的FILE_APPEND_DATA表示并发编写器下最大文件范围(文件“长度”)的增量为原子。这是由POSIX保证的,Linux,FreeBSD,OS X和Windows都正确实现了它。 Samba也正确地实现了它,v5之前的NFS没有,因为它缺乏以原子方式追加的有线格式功能。因此,如果您使用仅附加文件打开文件,除非涉及NFS,否则并发写入不会在任何主要操作系统上相互撕裂。
然而并发读取到原子追加可能看到撕裂的写入取决于操作系统,归档系统,以及打开文件的标志 - 最大文件范围的增量是原子的,但写入相对于读取的可见性可能是也可能不是原子的。以下是标志,操作系统和文件系统的快速摘要:
Microsoft Windows 10 with NTFS:update atomicity = 1 byte,包括10.0.10240,10.0.14393至少1Mb,可能是无限(*)。
Linux 4.2.6 with ext4:update atomicity = 1 byte
带有ZFS的FreeBSD 10.2:update atomicity =至少1Mb,可能是无限(*)带有NTFS的Microsoft Windows 10:更新原子性=直到并包括10.0.10240仅在页面对齐时最多4096字节,否则如果FILE_FLAG_WRITE_THROUGH关闭则为512字节,否则为64字节。请注意,这个原子性可能是PCIe DMA的一个特性而不是设计的。自10.0.14393以来,至少1Mb,可能是无限的(*)。
Linux 4.2.6 with ext4:update atomicity =至少1Mb,可能是无限(*)。请注意,早期使用ext4的Linux肯定没有超过4096字节,XFS肯定用于自定义锁定,但看起来最近Linux已经解决了这个问题。
带有ZFS的FreeBSD 10.2:update atomicity =至少1Mb,可能是无限(*)您可以在https://github.com/ned14/afio/tree/master/programs/fs-probe查看原始经验测试结果。注意我们只测试512字节倍数的撕裂偏移,所以我不能说在读 - 修改 - 写周期中是否会撕裂512字节扇区的部分更新。
因此,为了回答OP的问题,O_APPEND写入不会相互干扰,但是并发读取O_APPEND写入可能会在Linux上使用ext4看到撕裂写入,除非O_DIRECT打开,因此您的O_APPEND写入需要是扇区大小倍数。
(*)“可能无限”源于POSIX规范中的这些子句:
以下所有函数都应该是原子函数 其他在POSIX.1-2008中指定的效果进行操作时 常规文件或符号链接... [很多功能] ... read()... write()...如果两个线程分别调用其中一个函数,则每次调用 应该看到另一个呼叫的所有指定效果,或者 没有一个。 [Source]
和
写入可以针对其他读取和写入进行序列化。如果一个 文件数据的read()可以证明(通过任何方式)在a之后发生 write()的数据,它必须反映write(),即使调用 是由不同的过程。 [Source]
但反过来说:
此POSIX.1-2008卷未指定并发行为 从多个进程写入文件。应用程序应使用一些 并发控制的形式。 [Source]
答案 2 :(得分:21)
我写了一个脚本来经验测试最大原子附加大小。用bash编写的脚本生成多个工作进程,这些进程都将特定于工作程序的签名写入同一文件。然后它读取文件,查找重叠或损坏的签名。您可以在此blog post查看脚本的来源。
实际最大原子附加大小不仅因操作系统而异,而且因文件系统而异。
在Linux + ext3上,大小为4096,在Windows + NTFS上,大小为1024.有关更多大小,请参阅下面的注释。
答案 3 :(得分:15)
以下是标准所说的内容:http://www.opengroup.org/onlinepubs/009695399/functions/pwrite.html。
如果设置了文件状态标志的
O_APPEND
标志,则应在每次写入之前将文件偏移设置为文件的末尾,并且在更改文件偏移量和文件偏移量之间不应进行中间文件修改操作。写操作。