通常,当我们从多个进程附加到UNIX中的文件时,我们可以理所当然地做什么呢?是否有可能丢失数据(一个进程覆盖另一个进程的更改)?数据有可能被破坏吗?(例如,每个进程向日志文件中的每个追加追加一行,是否有可能两行被整顿?)如果从上述意义上说,追加不是原子的,那么确保互斥的最佳方法是什么?
通常,当我们从多个进程附加到UNIX中的文件时,我们可以理所当然地做什么呢?是否有可能丢失数据(一个进程覆盖另一个进程的更改)?数据有可能被破坏吗?(例如,每个进程向日志文件中的每个追加追加一行,是否有可能两行被整顿?)如果从上述意义上说,追加不是原子的,那么确保互斥的最佳方法是什么?
Answers:
小于“ PIPE_BUF”大小的写入应该是原子的。该长度至少应为512字节,尽管它可以轻易变大(Linux似乎将其设置为4096)。
假设您正在谈论所有完全符合POSIX的组件。例如,在NFS上并非如此。
但是,假设您写入在“ O_APPEND”模式下打开的日志文件并将行(包括换行符)的长度保持在“ PIPE_BUF”字节以下,则应该能够使多个写入者写入日志文件而不会出现任何损坏问题。任何中断都将在写入之前或之后到达,而不是在中间。如果希望文件完整性在重新启动后仍然存在,则还需要fsync(2)
在每次写入后都调用它,但是这样会降低性能。
说明:阅读评论和Oz Solomon的回答。我不确定O_APPEND
应该具有这种PIPE_BUF
原子性。这完全有可能是Linux的实现方式write()
,或者可能是由于底层文件系统的块大小所致。
PIPE_BUF
该页面上的有关语句仅适用于管道和FIFO,不适用于常规文件。
编辑: 2017年8月更新了最新的Windows结果。
作为拟议的Boost.AFIO的作者,我将为您提供测试代码和结果的链接的答案,该文件实现了异步文件系统和文件I / O C ++库。
首先,Windows上的O_APPEND或等效的FILE_APPEND_DATA表示在并发编写器下,最大文件范围的增量(文件“长度”)是原子的。POSIX保证了这一点,Linux,FreeBSD,OS X和Windows都正确实现了它。Samba也可以正确实现它,而v5之前的NFS则不正确,因为它缺少自动附加的有线格式功能。因此,如果您仅使用追加文件打开文件,则在任何主要操作系统上,并发写操作都不会彼此撕裂除非涉及NFS,否则。
但是,对原子追加的并发读取可能会发现写入中断,具体取决于操作系统,文件系统以及打开文件时使用的标志-最大文件范围的增量是原子的,但写入相对于读取的可见性可能会或可能不会是原子的。以下是标志,操作系统和归档系统的快速摘要:
具有NTFS的Microsoft Windows 10:更新原子性= 1个字节,直到10.0.10240(包括10.0.10240)从10.0.14393起至少1Mb,可能是无限(*)。
带有ext4的Linux 4.2.6:更新原子性= 1字节
具有ZFS的FreeBSD 10.2:更新原子性=至少1Mb,可能是无限(*)
带有NTFS的Microsoft Windows 10:仅当页面对齐时,更新atomicity =直到并包括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写入并发的读取可能会在带有ext4的Linux上看到撕裂的写入,除非O_DIRECT处于打开状态,这时您的O_APPEND写入将需要是扇区大小的倍数。
(*)“可能是无限的”源于POSIX规范中的以下子句:
当以下所有功能在常规文件或符号链接上运行时,以下所有功能在POSIX.1-2008中指定的效果上彼此都是原子的。[许多功能] ... read()... write( )...如果两个线程各自调用这些函数之一,则每个调用应要么看到另一个调用的所有指定作用,要么都不看到。[资源]
和
可以针对其他读取和写入对写入进行序列化。如果可以证明(通过任何方式)在数据的write()之后发生了文件数据的read(),则即使调用是由不同的进程进行的,它也必须反映出write()。[资源]
但是相反:
POSIX.1-2008的此卷没有指定从多个进程并发写入文件的行为。应用程序应使用某种形式的并发控制。[资源]
我编写了一个脚本来凭经验测试最大原子附加大小。用bash编写的脚本产生了多个工作进程,这些工作进程都将特定于工作程序的签名写入同一文件。然后,它读取文件,查找重叠或损坏的签名。您可以在此博客文章中看到脚本的源代码。
实际的最大原子附加大小不仅随操作系统而异,而且随文件系统而异。
在Linux + ext3上,大小为4096,在Windows + NTFS上,大小为1024。有关更多大小,请参见下面的注释。
echo $line >> $OUTPUT_FILE
都会导致一次调用。write
$line
这是标准所说的内容:http : //www.opengroup.org/onlinepubs/009695399/functions/pwrite.html。
如果
O_APPEND
设置了文件状态标志的标志,则应在每次写入之前将文件偏移量设置到文件的末尾,并且在更改文件偏移量和写入操作之间不得进行任何中间文件修改操作。
fsync(2)
提供尽可能多的保证sync(2)
,并且对性能的影响不大。