写序列或单写

时间:2019-10-12 18:15:12

标签: c unix atomicity

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

write(fd,"ABCD",4);

比这样做更好:

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

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

谢谢

3 个答案:

答案 0 :(得分:1)

您在这里担心原子性是正确的。

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

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)放在父项中,在waitpid之后?)

答案 1 :(得分:0)

问题仅仅是效率:每个调用都需要将参数压入堆栈,然后将其从函数中拉出,然后返回。当您反复拨打电话时,所有这些操作都是不必要的。

答案 2 :(得分:0)

POSIX要求write的系统调用在2.9.7 Thread Interactions with Regular File Operations中是原子的:

  

如果两个线程各自调用这些功能之一[write是其中之一],则每个调用应要么看到另一个调用的所有指定效果,要么都不看到。

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

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

因此,您可能希望减少write通话次数。