#include <stdio.h>
#include <unistd.h>
int main()
{
fork();
fork() && fork() || fork();
fork();
printf("forked\n");
return 0;
}
如何计算程序执行后产生的进程数量,这可能会让人感到困惑。请帮我找出答案。
平台--UBUNTU 10.04
让我们跟随 fork 树,假设没有一个分支失败
fork();
现在我们有两个进程,目前谁是子进程,谁是父进程并不重要,称它们为 p1 和 p2
fork()
这两个进程都生成了另一个子进程,所以我们有了四个进程,对于其中的两个进程 (p3 和 p4),结果是零,而对于另外两个进程 (p1 和 p2),结果是非零
p1和p2再次进行分叉,得到p5和p6,总共有六个进程。对于p1和p2,&& fork()
&&
的值为真,因此它们在这一行不会再次分叉。对于p3、p4、p5、p6,&&
的值为假,因此它们会进行分叉。
|| fork();
在这里,生成四个新进程,总共有6 + 4 = 10个。
fork();
每个10个进程都再次分叉,总共变成20个。
不应该像这样使用fork()。绝对不要这样做。然而,在现实生活中,你也不需要这样做。
如何正确使用:
int main() {
/* code */
pid_t pid = fork();
if (pid < 0) {
/* error, no child process spawned */
}
if (pid > 0) {
/* we are the parent process, pid is the process ID of the ONE child process spawned */
}
/* else, we are the child process, running exactly one command later the fork() was called in the parent. */
/* some more code */
return 0;
}
fork();
fork
系统调用返回一个整数:在父进程中是子进程的PID,在子进程中为0。如果发生错误,则不创建进程并返回-1。
||
和&&
是逻辑运算符。
如果在评估左操作数后已知运算符的结果,则必须短路(即不评估右操作数):
||
运算符,如果左操作数!= 0,则不评估其右操作数&&
运算符,如果左操作数== 0,则不评估其右操作数保存文件,命名为fork-count.c
。然后使用gcc fork-count.c -o fork-count
进行编译。接着你可以运行它,并用./fork-count | wc -l
来计算输出的行数。
ideone
,你会得到不可靠的结果,因为它不能正确地捕获分叉进程的输出。 - Michael Mior我已经在Geeks for Geeks上找到了这个问题的正确解释:
fork()系统调用将进程作为二叉树生长的叶子生成。如果我们调用fork()两次,它将生成22 = 4个进程。所有这4个进程都形成了二叉树的叶子节点。一般来说,如果我们处于第l层,并且无条件地调用fork(),则在第(l+1)层将有2l个进程。这相当于在第(l+1)层的二叉树中具有最大子节点数。
例如,假设我们无条件地调用了3次fork()。我们可以使用具有3个级别的完整二叉树表示生成的进程。在第3层,我们将有23 = 8个子节点,这对应于正在运行的进程数。
C/C++逻辑运算符的注意事项:
逻辑运算符&&比||更具优先级,并且具有从左到右的结合性。执行左操作数后,将估计最终结果,并且右操作数的执行取决于左操作数的结果以及操作类型。
在AND(&&)的情况下,在评估左操作数后,仅当左操作数评估为非零时才评估右操作数。在OR(||)的情况下,在评估左操作数后,仅当左操作数评估为零时才评估右操作数。
fork()的返回值:
fork()的手册引用了以下关于返回值的摘录:
“成功时,在父进程中返回子进程的PID,在子进程中返回0。失败时,在父进程中返回-1,不创建子进程,并适当设置errno。”
PID类似于进程句柄,表示为无符号整数。我们可以得出结论,fork()将在父进程中返回非零值,在子进程中返回零。让我们分析程序。为了方便表示,将每个fork()标记如下:
#include <stdio.h>
int main()
{
fork(); /* A */
( fork() /* B */ &&
fork() /* C */ ) || /* B and C are grouped according to precedence */
fork(); /* D */
fork(); /* E */
printf("forked\n");
return 0;
}
前两个fork()调用是无条件的。
在第0级,我们只有主进程。主进程(图中的m)将创建子进程C1,并且两者都将继续执行。子进程按照它们创建的顺序进行编号。
在第1级,我们有m和C1正在运行,并准备执行fork() - B。(注意B、C和D被命名为&&和||运算符的操作数)。初始表达式B将在每个子进程和父进程在此级别运行时执行。
在第2级,由于m和C1执行了fork() - B,我们有m和C1作为父进程,C2和C3作为子进程。
fork() - B的返回值在父进程中为非零,在子进程中为零。由于第一个运算符是&&,因为返回值为零,子进程C2和C3将不会执行下一个表达式(fork() - C)。父进程m和C1将继续执行fork() - C。子进程C2和C3将直接执行fork() - D,以计算逻辑或操作的值。
在第3级别,我们有m、C1、C2、C3作为运行中的进程,C4、C5作为子进程。表达式现在简化为((B && C) || D),此时(B && C)的值是明显的。在父进程中是非零,在子进程中是零。因此,父进程知道整体的B && C || D的结果,将跳过执行fork() - D。由于在子进程中(B && C)计算为零,它们将执行fork() - D。需要注意的是,在第2级别创建的子进程C2和C3也将按照上述方法运行fork() - D。
fork() && fork() || fork()
。你可以分开这一行并添加更多的调试输出。 - ThiefMasterfork()
将返回TRUE
,并且几乎总是成功,除非您达到了进程的分叉限制,因此第二行的前两个分叉调用将执行,但不是第三个,由于短路。[啊,现在我记起来了!——fork对于一个进程返回true,对于另一个进程返回false,并且您将在两个进程中都从上次离开的地方继续。可能结果将是一种分形扩展,也许难以预测。] - Hot Licks