使用一系列 write() 而不是单个 write()

Use a sequence of write() instead a single write()

我在 Unix 系统上用 C 编程。我知道:

write(fd,"ABCD",4);

比那样做更好:

write(fd, "A", 1);
write(fd, "B", 1);
write(fd, "C", 1);
write(fd, "D", 1);

显然,我不是谈论代码的易读性,但我想知道第二种方法会带来什么问题。我们谈论执行的原子性吗?如果是,为什么?

谢谢

问题很简单:每次调用都需要将参数压入堆栈,然后在函数中将它们拉出,然后返回。当你重复调用时,这些都是不必要的重复。

POSIX 要求 write 系统调用在 2.9.7 Thread Interactions with Regular File Operations:

中是原子的

If two threads each call one of these functions [write is one of them], each call shall either see all of the specified effects of the other call, or none of them.

但是,write 不需要写下请求的全部金额。原子写入的最大大小取决于文件系统。对于管道,它是 PIPE_BUF.

请注意,write 是一个系统调用,这些调用比常规调用昂贵得多。因此,流行的方法是先写入缓冲区,当缓冲区已满或需要立即刷新时才调用 write 系统调用。这就是 C I/O 库 fwrite 为您所做的。

因此,您可能希望尽量减少 write 次调用。

你在这里担心原子性是对的。

如果另一个进程或同一进程中的另一个线程也在写入同一通信通道,则

write(fd, "ABCD", 4);

将自动写入四个字节 "ABCD",因为其他写入器的输出不能出现在这四个字节的中间。另一方面,

write(fd, "A", 1);
write(fd, "B", 1);
write(fd, "C", 1);
write(fd, "D", 1);

将单独写入每个字节,其他写入器的输出可以出现在字母之间。

但是,只有 short 写入才能保证原子性,其中第三个参数小于常量 PIPE_BUF。如果您写入的数据多于此,则无法确保其他写入器的输出不会出现在中间。 PIPE_BUF 可能小到 512,但通常比现在的要大一些。

最简单的亲自查看方法是使用 fork 并写入标准输出:

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

int main(void)
{
    pid_t pid = fork();
    if (pid < 0) {
        perror("fork");
        return 1;

    } else if (pid == 0) {
        // child
        write(1, "ABCD", 4);  sleep(1);
        write(1, "A", 1);     sleep(1);
        write(1, "B", 1);     sleep(1);
        write(1, "C", 1);     sleep(1);
        write(1, "D", 1);
        return 0;

    } else {
        // parent
        write(1, "1234", 4);  sleep(1);
        write(1, "1", 1);     sleep(1);
        write(1, "2", 1);     sleep(1);
        write(1, "3", 1);     sleep(1);
        write(1, "4", 1);

        waitpid(pid, 0, 0);
        write(1, "\n", 1);
        return 0;
    }
}

该程序将打印类似 1234ABCD1AB23CD4 的内容。第一个 ABCD 将始终作为一个组出现,第一个 1234 也将始终作为一个组出现,但任何一个都可以是第一个;第二个 ABCD1234 可能会任意混淆在一起。

(小测验:为什么我把最后的write(1, "\n", 1)放在parent中,之后waitpid?)