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

jit*_*hsk 14 c unix file-io operating-system mpi

从这里:文件是否在UNIX中附加原子

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

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

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

规格:Opensuse 11.3 64位的OpenMPI 1.4.3

编译为: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;
}
Run Code Online (Sandbox Code Playgroud)

caf*_*caf 16

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

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

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

  • @KVM:[来自Linus Torvalds的这个相当有趣的帖子](http://article.gmane.org/gmane.linux.kernel/43445)暗示`O_APPEND`文件上的单个`write()`应该是原子的Linux,至少在"类UNIX文件系统"上 - 给出的例子是日志文件.请注意,虽然NFS for one肯定不会以这种方式运行. (5认同)
  • 请注意,[POSIX 7 `write` 文档](http://pubs.opengroup.org/onlinepubs/9699919799/functions/write.html) 指出:“如果设置了文件状态标志的 `O_APPEND` 标志,则文件在每次写入之前,偏移量应设置为文件末尾,并且**在更改文件偏移量和写入操作之间不应发生中间文件修改操作**。” 这听起来像是原子性的保证。但请注意,这并不能保证*完整性* - `write()` 不保证写入请求的所有字节,尽管 IME 不会部分写入实际文件。 (4认同)
  • @KVM:是的,但是Linus正在讨论UNIX和Linux的传统行为,而不是任何书面标准强制要求的行为. (2认同)
  • 我之前提到的电子邮件的[备用存档链接](http://lkml.iu.edu/hypermail/linux/kernel/0208.0/0251.html). (2认同)

Nia*_*las 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( )...如果两个线程分别调用其中一个函数,则每个调用应该看到另一个调用的所有指定效果,或者没有一个.[资源]

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

但反过来说:

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

对所有这三个要求的安全解释将表明,在同一文件中重叠一定范围的所有写入必须相对于彼此进行序列化,并且读取使得撕裂的写入从未出现在读者身上.

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

当然,仅仅因为标准需要这些语义并不意味着实现符合,但实际上带有ZFS的FreeBSD表现完美,最近的Windows(10.0.14393)和NTFS表现完美,而且如果O_DIRECT打开,最近的带有ext4的Linux表现正常.如果您想了解主要操作系统和文件系统符合标准的详细信息,请参阅此答案


Cel*_*ada 5

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

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

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

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

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

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