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

时间:2012-10-17 20:33:54

标签: c unix file-io operating-system mpi

从这里开始:Is file append atomic in UNIX

考虑多个进程打开同一个文件并附加到它的情况。 O_APPEND保证寻找到文件的末尾然后开始写操作是原子的。因此,只要每个写入大小为< = PIPE_BUF,多个进程就可以附加到同一个文件中,并且任何进程都不会覆盖任何其他进程的写入。

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

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

规格: OpenMPI 1.4.3上 打开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;
}

3 个答案:

答案 0 :(得分:15)

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

  

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

...这意味着你自己 - 不同的UNIX喜欢会提供不同的保证。

答案 1 :(得分:12)

首先,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()可以证明(通过任何方式)在a之后发生   write()的数据,它必须反映write(),即使调用   是由不同的过程。 [Source]

但反过来说:

  

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

对所有这三个要求的安全解释将表明,在同一文件中重叠某个范围的所有写入必须相互序列化,并且读取使得撕裂的写入永远不会出现给读者。

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

当然,仅仅因为标准要求这些语义并不意味着实现符合,尽管实际上带有ZFS的FreeBSD表现完美,最近的Windows(10.0.14393)和NTFS表现完美,而最近的带有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

答案 2 :(得分:5)

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

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

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

这并不意味着你可以指望这一直是真的。你永远不知道什么时候可能不是真的:

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

因此,您的实验无法证明您可以指望非交错式写入。