fork()
创建一个新进程,子进程从父进程的当前状态开始执行。
这是我在Linux中了解fork()
的事情。
因此,相应的代码如下:
int main() {
printf("Hi");
fork();
return 0;
}
需要按照上述方式仅打印一次“Hi”。
但是在使用gcc编译的Linux中执行上述操作时,它会打印“Hi”两次。
有人可以向我解释使用fork()
时实际发生了什么,以及我是否正确了解了fork()
的工作情况?
答案 0 :(得分:36)
(在用户@Jack的评论中加入一些解释) 当您将某些内容打印到“标准输出”标准输出(通常是计算机显示器,虽然您可以将其重定向到文件)时,它最初会存储在临时缓冲区中。
fork的两端都继承了未刷新的缓冲区,因此当fork的每一侧都命中return语句并结束时,它会被刷新两次。
在你分叉之前,你应该fflush(stdout);
来刷新缓冲区,以便孩子不会继承它。
stdout到屏幕(而不是当你将它重定向到文件时)实际上是由行结束缓冲的,所以如果你完成printf("Hi\n");
你就不会遇到这个问题,因为它会冲洗了缓冲区本身。
答案 1 :(得分:22)
printf("Hi");
实际上并不会立即在屏幕上打印“Hi”字样。它所做的是在stdout
缓冲区填充单词“Hi”,然后在缓冲区被“刷新”后显示。在这种情况下,stdout
指向您的监视器(假设)。在这种情况下,缓冲区将在它满时刷新,强制刷新时刷新,或者(最常见)在打印换行符(“\ n”)时刷新。由于在调用fork()
时缓冲区仍然是满的,因此父进程和子进程都继承它,因此它们在刷新缓冲区时都会打印出“Hi”。如果在调用fork之前调用fflush(stout);
,它应该可以工作:
int main() {
printf("Hi");
fflush(stdout);
fork();
return 0;
}
或者,正如我所说,如果您在printf
中添加换行符,它也应该有效:
int main() {
printf("Hi\n");
fork();
return 0;
}
答案 2 :(得分:8)
通常,fork()两侧的库使用打开的句柄/对象是非常不安全的。
这包括C标准库。
fork()使两个进程成为一个进程,没有库可以检测到它正在发生。因此,如果两个进程继续使用相同的文件描述符/套接字等运行,它们现在具有不同的状态但共享相同的文件句柄(技术上它们具有副本,但是相同的底层文件)。这会让糟糕的事情发生。
fork()导致此问题的示例
如何解决一般情况:
无论
a)在fork()之后,立即调用exec(),可能在同一个二进制文件上(使用必要的参数来实现你打算做的任何工作)。这很容易。
b)分叉后,不要使用任何现有的打开手柄或依赖它们的库对象(打开新的可以);尽快完成你的工作,然后调用_exit()(而不是exit())。不要从调用fork的子例程返回,因为这可能会调用C ++析构函数等,这可能会对父进程的文件描述符造成不良影响。这很容易。
c)分叉后,以某种方式清理所有物体,并让它们在孩子继续之前处于理智状态。例如关闭基础文件描述符而不刷新在父级中复制的缓冲区中的数据。这很棘手。
c)与Apache的相似。
答案 3 :(得分:3)
printf()
做缓冲。您是否尝试过打印到stderr
?
答案 4 :(得分:2)
技术答案:
当使用fork()时,你需要确保exit()没有被调用两次(从main掉下来与调用exit()相同)。孩子(或很少是父母)需要调用_exit。另外,不要在孩子身上使用stdio。那只是在寻找麻烦。
有些库有一个fflushall(),你可以在fork()之前调用它,使子进程中的stdio安全。在这种特殊情况下,它也会使exit()安全,但在一般情况下则不然。