了解来自多个进程的并发文件写入

Posted

技术标签:

【中文标题】了解来自多个进程的并发文件写入【英文标题】:Understanding concurrent file writes from multiple processes 【发布时间】:2012-10-08 05:14:34 【问题描述】:

从这里:Is file append atomic in UNIX

考虑多个进程打开同一个文件并附加到它的情况。 O_APPEND 保证寻找到文件末尾然后开始写操作是原子的。因此,多个进程可以追加到同一个文件,只要每个写入大小为

我编写了一个测试程序,其中多个进程打开并写入同一个文件 (write(2))。我确保每个写入大小 > PIPE_BUF (4k)。我期待看到进程覆盖其他人的数据的情况。但这不会发生。我测试了不同的写入大小。这只是运气还是没有发生这种情况的原因? 我的最终目标是了解附加到同一文件的多个进程是否需要协调它们的写入。

这是完整的程序。每个进程都会创建一个 int 缓冲区,用它的rank 填充所有值,打开一个文件并写入它。

规格: OpenMPI 1.4.3 开启 Opensuse 11.3 64 位

编译为:mpicc -O3 test.c, 运行为:mpirun -np 8 ./a.out

#include <stdio.h>
#include <stdlib.h>
#include <mpi.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

int 
main(int argc, char** argv) 
    int rank, size, i, bufsize = 134217728, fd, status = 0, bytes_written, tmp_bytes_written;
    int* buf;
    char* filename = "/tmp/testfile.out";

    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    MPI_Comm_size(MPI_COMM_WORLD, &size);

    buf = (int*) malloc (bufsize * sizeof(int));   
    if(buf == NULL) 
        status = -1;
        perror("Could not malloc");
        goto finalize;
    
    for(i=0; i<bufsize; i++) 
        buf[i] = rank;

    if(-1 == (fd = open(filename, O_APPEND|O_WRONLY, S_IWUSR))) 
        perror("Cant open file");
        status = -1;
        goto end;
        exit(-1);
    

    bytes_written = 0;
    if(bufsize != (tmp_bytes_written = write(fd, buf, bufsize))) 
        perror("Error during write");
        printf("ret value: %d\n", tmp_bytes_written);
        status = -1;
        goto close;
    

close:
    if(-1 == close(fd)) 
        perror("Error during close");
        status = -1;
    
end:
    free(buf);
finalize:
    MPI_Finalize();
    return status;

【问题讨论】:

perror( filename );perror( "Cant open file"); 有用得多 我在 linux 机器上做了同样的测试(centos 7 3.10.0-327.13.1.el7.x86_64),我看到了你想要的行为。参考***.com/questions/38219512/…。 【参考方案1】:

小于PIPE_BUF 的写入原子性仅适用于管道和 FIFO。对于文件写入,POSIX 表示:

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

...这意味着您只能靠自己 - 不同的 UNIX-like 将提供不同的保证。

【讨论】:

我如何知道我的 Linux 保证了什么?这是针对特定 Linux 的某处记录的吗? @KVM: This rather amusing posting from Linus Torvalds 意味着O_APPEND 文件上的单个write() 在 Linux 上应该是原子的,至少在“类 UNIX 文件系统”上是原子的 - 给出的示例是日志文件。请注意,尽管 NFS 的行为肯定不是这种方式。 @KVM:是的,但 Linus 谈论的是 UNIX 和 Linux 的传统行为,而不是任何书面标准的强制要求。 我之前提到的电子邮件的alternate archive link。 请注意POSIX 7 write documentation 声明:“如果设置了文件状态标志的O_APPEND 标志,则文件偏移量应在每次写入之前设置为文件末尾,并且 在更改文件偏移量和写入操作之间不应发生中间文件修改操作。"这听起来像是原子性的保证。但是请注意,这并不能保证完整性 - write() 不能保证写入所有请求的字节,尽管不会发生 IME 部分写入实际文件的情况。【参考方案2】:

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

这并没有说明读取是否会看到撕裂的写入,并且在 POSIX 上说了以下关于 read() 和 write() 对常规文件的原子性:

以下所有函数对于每个函数都应是原子的 其他在 POSIX.1-2008 中指定的效果中运行时 常规文件或符号链接 ... [许多功能] ... read() ... write() ... 如果两个线程各自调用这些函数之一,则每个调用 要么看到另一个呼叫的所有指定效果,要么 他们都没有。 [Source]

写入可以相对于其他读取和写入进行序列化。如果一个 文件数据的 read() 可以被证明(通过任何方式)发生在 数据的 write(),它必须反映 write(),即使调用 由不同的工艺制成。 [Source]

但反过来:

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

对所有这三个要求的安全解释表明,在同一文件中与范围重叠的所有写入都必须相对于彼此进行序列化,并且对于读取而言,撕裂的写入永远不会出现在读者面前。

一个不太安全但仍然允许的解释可能是读取和写入仅在同一进程内的线程之间相互序列化,而进程之间的写入仅相对于读取进行序列化(即存在顺序一致的 i/o 排序进程中的线程之间,但进程之间的 i/o 只是获取-释放)。

当然,仅仅因为标准要求这些语义并不意味着实现符合,尽管事实上带有 ZFS 的 FreeBSD 表现完美,最近的带有 NTFS 的 Windows (10.0.14393) 表现完美,而最近的带有 ext4 的 Linux 表现正确,如果O_DIRECT 开启。 If you would like more detail on how well major OS and filing systems comply with the standard, see this answer

【讨论】:

我从未听说过 NFS v5? @ostrokach 它已经开发多年了。他们在我阅读上述内容的地方有一个邮件列表。【参考方案3】:

这不是运气,从某种意义上说,如果您深入研究内核,您可能会证明在您的特定情况下,永远不会发生一个进程的write 与另一个进程交错。我假设:

您没有达到任何文件大小限制 您没有填充创建测试文件的文件系统 该文件是常规文件(不是套接字、管道或其他东西) 文件系统是本地的 缓冲区不跨越多个虚拟内存映射(这个已知是真的,因为它是malloc()ed,它把它放在堆上,它是连续的。 当write() 忙时,不会中断、通知或跟踪进程。 没有磁盘 I/O 错误、RAM 故障或任何其他异常情况。 (也许是其他人)

您可能确实会发现,如果所有这些假设都成立,那么您碰巧使用的操作系统的内核总是通过对以下内容的单个原子连续写入来完成单个 write() 系统调用文件。

这并不意味着您可以指望这总是正确的。你永远不知道什么时候它可能不是真的:

程序在不同的操作系统上运行 文件移动到 NFS 文件系统 进程在write() 正在进行时收到一个信号,write() 返回部分结果(比请求的字节少)。不确定 POSIX 是否真的允许这种情况发生,但我进行了防御性编程! 等等……

因此,您的实验无法证明您可以依赖非交错写入。

【讨论】:

以上是关于了解来自多个进程的并发文件写入的主要内容,如果未能解决你的问题,请参考以下文章

C#使用读写锁三句代码简单解决多线程并发写入文件时提示“文件正在由另一进程使用,因此该进程无法访问此文件”的问题

在将输出写入文件时使用多处理显示来自子进程的实时输出

python日志打印和写入并发简易版本实现

多个线程同时写入多个文件 - 它正被另一个进程使用

使用来自 Python 的 1 个进程使用 HDF5 和 MPI 写入/读取大文件

DLL 如何处理来自多个进程的并发?