为什么我的multi-process写程序没有触发并发冲突?

Why doesn't my multi-process writing program trigger concurrent conflict?

我试图通过让多个进程写入同一个文件来触发一些并发冲突,但无法:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/wait.h>

void concurrent_write()
{
    int create_fd = open("bar.txt", O_CREAT | O_TRUNC, 0644);
    close(create_fd);
    int repeat = 20;
    int num = 4;

    for (int process = 0; process < num; process++)
    {
        int rc = fork();
        if (rc == 0)
        {
            // child
            int write_fd = open("bar.txt", O_WRONLY | O_APPEND, 0644);
            for (int idx = 0; idx < repeat; idx++)
            {
                sleep(1);
                write(write_fd, "child writing\n", strlen("child writing\n"));
            }
            close(write_fd);

            exit(0);
        }
    }

    for (int process = 0; process < num; process++)
    {
        wait(NULL);
        // wait for all children to exits
    }

    printf("write to `bar.txt`\n%d lines written by %d process\n", repeat * num, num);

    printf("wc:");
    if (fork() == 0)
    {
        // child
        char *args[3];
        args[0] = strdup("wc");
        args[1] = strdup("bar.txt");
        args[2] = NULL;
        execvp(args[0], args);
    }
}

int main(int argc, char *argv[])
{
    concurrent_write();

    return 0;
}

这个程序 fork #num children 然后让它们全部写入 [​​=13=] 行到一个文件。但是每次(但是我更改 #repeat#num)我得到的结果都是相同的,即 bar.txt(输出文件)的长度与总写入行数相匹配。为什么没有触发并发冲突?

写入文件可分为two-step个过程:

  1. 找到你想写的地方。
  2. 将数据写入文件。

您打开一个带有标志 O_APPEND 的文件,它确保 two-step 进程是原子的。所以,你总能找到你设置的文件行数。

查看 open(2) man page:

O_APPEND

The file is opened in append mode. Before each write(2), the file offset is positioned at the end of the file, as if with lseek(2). The modification of the file offset and the write operation are performed as a single atomic step.

本质上,O_APPEND 的主要设计特点之一就是防止您提到的那种“并发冲突”。典型示例是多个进程必须写入的日志文件。使用 O_APPEND 确保它们的消息不会相互覆盖。

此外,单个 write 调用写入的所有数据都是原子写入的,因此只要您的 write("child writing\n") 成功写入所有字节(对于常规文件通常会这样),它们将不与任何其他此类消息的字节交错。

首先,带有 O_APPEND 标志的 write() 调用应该是原子的。 Per POSIX write():

If the O_APPEND flag of the file status flags is set, the file offset shall be set to the end of the file prior to each write and no intervening file modification operation shall occur between changing the file offset and the write operation.

但是当有多个线程或进程对同一个文件进行并行 write() 调用时,这还不够——这不能保证并行 write() 调用是原子的。

POSIX 确实保证 parallel write() calls are also atomic:

All of the following functions shall be atomic with respect to each other in the effects specified in POSIX.1-2017 when they operate on regular files or symbolic links:

...

write()

...

另见 Is file append atomic in UNIX?

不过要小心。阅读该问题及其答案表明,Linux 文件系统如 ext3 不符合 POSIX 一旦你通过了一个相对较小的操作,或者可能如果你跨页 and/or文件系统扇区边界。我怀疑 XFS 和 ZFS 会更好地支持 write() 原子性,因为它们的起源。

其中 none 适用于 Windows。