118

一般来说,当我们从多个进程附加到 UNIX 中的一个文件时,我们可以认为什么是理所当然的?是否有可能丢失数据(一个进程覆盖另一个进程的更改)?数据有可能被破坏吗?(例如,每个进程在每次追加到日志文件时追加一行,是否有可能两行被破坏?)如果追加在上述意义上不是原子的,那么确保互斥的最佳方法是什么?

4

4 回答 4

67

小于“PIPE_BUF”大小的写入应该是原子的。这应该至少是 512 字节,尽管它很容易变得更大(linux 似乎将它设置为 4096)。

这假设您正在谈论所有完全符合 POSIX 的组件。例如,在 NFS 上不是这样。

但是假设您写入以“O_APPEND”模式打开的日志文件并将行(包括换行符)保持在“PIPE_BUF”字节下,那么您应该能够将多个写入者写入日志文件而不会出现任何损坏问题。任何中断都会在写入之前或之后到达,而不是在中间。如果您希望文件完整性在重新启动后仍然存在,您还需要fsync(2)在每次写入后调用,但这对性能来说很糟糕。

澄清:阅读评论和Oz Solomon 的回答。我不确定它O_APPEND应该具有那种PIPE_BUF大小的原子性。完全有可能这就是 Linux 的实现方式write(),或者可能是由于底层文件系统的块大小。

于 2009-07-20T16:39:03.490 回答
39

编辑: 2017 年 8 月更新了最新的 Windows 结果。

作为提议的Boost.AFIO的作者,我将为您提供测试代码和结果的链接的答案,它实现了异步文件系统和文件 i/o C++ 库。

首先,Windows 上的 O_APPEND 或等效的 FILE_APPEND_DATA 意味着最大文件范围(文件“长度”)的增量在并发写入者下是原子的。POSIX 保证了这一点,Linux、FreeBSD、OS X 和 Windows 都正确实现了它。Samba 也正确实现了它,v5 之前的 NFS 没有,因为它缺乏自动附加的有线格式功能。因此,如果您以仅附加方式打开文件,则并发写入不会在任何主要操作系统上相互撕裂,除非涉及 NFS。

但是,对原子附加的并发读取可能会看到写入撕裂,具体取决于操作系统、文件系统以及您打开文件时使用的标志 - 最大文件范围的增量是原子的,但写入相对于读取的可见性可能会也可能不会是原子的。以下是标志、操作系统和文件系统的快速摘要:


没有 O_DIRECT/FILE_FLAG_NO_BUFFERING:

带有 NTFS 的 Microsoft Windows 10:更新原子性 = 1 字节,直到并包括 10.0.10240,从 10.0.14393 至少 1Mb,可能是无限的 (*)。

带有 ext4 的 Linux 4.2.6:更新原子性 = 1 字节

带有 ZFS 的 FreeBSD 10.2:更新原子性 = 至少 1Mb,可能是无限的 (*)

O_DIRECT/FILE_FLAG_NO_BUFFERING:

带有 NTFS 的 Microsoft Windows 10:更新原子性 = 直到并包括 10.0.10240,仅当页面对齐时最多 4096 字节,否则如果 FILE_FLAG_WRITE_THROUGH 关闭,则为 512 字节,否则为 64 字节。请注意,这种原子性可能是 PCIe DMA 的一个特性,而不是设计的。从 10.0.14393 开始,至少 1Mb,可能是无限的 (*)。

带有 ext4 的 Linux 4.2.6:更新原子性 = 至少 1Mb,可能是无限的 (*)。请注意,带有 ext4 的早期 Linux 肯定不会超过 4096 字节,XFS 肯定曾经有自定义锁定,但看起来最近的 Linux 终于修复了这个问题。

带有 ZFS 的 FreeBSD 10.2:更新原子性 = 至少 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( ) ... 如果两个线程各自调用这些函数之一,则每个调用要么看到另一个调用的所有指定效果,要么一个都看不到。[来源]

写入可以相对于其他读取和写入进行序列化。如果可以(通过任何方式)证明文件数据的 read() 发生在数据的 write() 之后,则它必须反映 write(),即使调用是由不同的进程进行的。[来源]

但反过来:

本卷 POSIX.1-2008 未指定从多个进程并发写入文件的行为。应用程序应该使用某种形式的并发控制。[来源]

您可以在此答案中阅读有关这些含义的更多信息

于 2016-02-07T17:19:56.167 回答
30

我编写了一个脚本来凭经验测试最大原子附加大小。该脚本以 bash 编写,生成多个工作进程,这些进程都将特定于工作人员的签名写入同一个文件。然后它读取文件,寻找重叠或损坏的签名。您可以在此博客文章中查看脚本的源代码。

实际的最大原子附加大小不仅因操作系统而异,而且因文件系统而异。

在 Linux+ext3 上,大小为 4096,在 Windows+NTFS 上,大小为 1024。有关更多大小,请参阅下面的评论。

于 2014-06-17T18:22:59.257 回答
16

这是标准所说的:http ://www.opengroup.org/onlinepubs/009695399/functions/pwrite.html 。

如果O_APPEND设置了文件状态标志的标志,则文件偏移量应在每次写入之前设置到文件末尾,并且在更改文件偏移量和写入操作之间不应发生中间文件修改操作。

于 2009-07-20T17:06:05.603 回答