使用一系列 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
也将始终作为一个组出现,但任何一个都可以是第一个;第二个 ABCD
和 1234
可能会任意混淆在一起。
(小测验:为什么我把最后的write(1, "\n", 1)
放在parent中,在之后waitpid
?)
我在 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
也将始终作为一个组出现,但任何一个都可以是第一个;第二个 ABCD
和 1234
可能会任意混淆在一起。
(小测验:为什么我把最后的write(1, "\n", 1)
放在parent中,在之后waitpid
?)