在 For 循环中 fork()会发生什么

我一直在试图理解 fork()的行为。这次是在 for-loop中。请观察以下代码:

#include <stdio.h>


void main()
{
int i;


for (i=0;i<3;i++)
{
fork();


// This printf statement is for debugging purposes
// getppid(): gets the parent process-id
// getpid(): get child process-id


printf("[%d] [%d] i=%d\n", getppid(), getpid(), i);
}


printf("[%d] [%d] hi\n", getppid(), getpid());
}

以下是输出结果:

[6909][6936] i=0
[6909][6936] i=1
[6936][6938] i=1
[6909][6936] i=2
[6909][6936] hi
[6936][6938] i=2
[6936][6938] hi
[6938][6940] i=2
[6938][6940] hi
[1][6937] i=0
[1][6939] i=2
[1][6939] hi
[1][6937] i=1
[6937][6941] i=1
[1][6937] i=2
[1][6937] hi
[6937][6941] i=2
[6937][6941] hi
[6937][6942] i=2
[6937][6942] hi
[1][6943] i=2
[1][6943] hi

我是一个非常视觉化的人,所以对我来说,真正理解事物的唯一方法就是用图表。我的导师说会有8个 语句。我编写并运行代码,确实有8个 语句。但我真的不明白。所以我画了下面这张图:

enter image description here

更新图表以反映评论:)

观察结果:

  1. 父进程(main)必须迭代循环3次,然后调用 printf
  2. 在父 for-loop 的每次迭代中,都会调用 fork ()
  3. 在每次 fork ()调用之后,i 被递增,因此每个子元素在 i 被递增之前都会启动一个 for 循环
  4. 在每个 for 循环的末尾,都会打印出“ hi”

以下是我的问题:

  • 我的图表正确吗?
  • 为什么在输出中有 i=0实例?
  • 在 fork ()之后,i的什么值被传递给每个子级?如果结转相同的 i值,那么什么时候“分叉”停止?
  • 2^n - 1是否总是一种计算分叉子孩子数量的方法?那么,这里的 n=3,意思是 2^3 - 1 = 8 - 1 = 7的孩子,哪个是正确的?
74711 次浏览
  1. 是的,它是正确的。(见下文)
  2. 不,i++是执行 之后fork的调用,因为这是 for循环的工作方式。
  3. 如果一切顺利,是的。但是,请记住,fork可能会失败。

关于第二点的一点解释:

for (i = 0;i < 3; i++)
{
fork();
}

类似于:

i = 0;
while (i < 3)
{
fork();
i++;
}

所以分叉进程(父进程和子进程)中的 i是增量之前的值。但是,增量是在 fork()之后立即执行的,因此在我看来,图表可以被视为正确的。

以下是如何理解它,从 for循环开始。

  1. 循环从父级 i == 0开始

  2. fork(),创建子1。

  3. 现在有两个进程,它们都打印 i=0

  4. 循环在两个进程中都重新启动,现在是 i == 1

  5. 父母和孩子1 fork(),创造孩子2和3。

  6. 您现在有了四个进程。所有四个进程都打印 i=1

  7. 循环在所有四个进程中重新启动,现在是 i == 2

  8. 父母和孩子1到3都是 fork(),创造孩子4到7。

  9. 您现在有八个进程。所有八个进程都打印 i=2

  10. 循环在所有八个进程中重新启动,现在是 i == 3

  11. 循环在所有八个进程中终止,因为 i < 3不再为真。

  12. 所有八个进程都打印 hi

  13. 所有八个进程都将终止。

所以你得到 0打印2次,1打印4次,2打印8次,hi打印8次。

逐一回答你们的问题:

我的图表正确吗?

是的,本质上来说,这是一个非常好的图表。

也就是说,如果您将 i=0等标签解释为引用完整循环迭代,那么它就是正确的。然而,图 没有显示的是,在每个 fork()之后,fork()调用之后的当前循环迭代的一部分也由分叉子进程执行。

为什么在输出中有两个 i=0实例?

因为 printf()fork()之后,所以它由父进程和刚刚分支的子进程执行。如果将 printf()移到 fork()之前,它将只由父进程执行(因为子进程还不存在)。

fork()之后,每个孩子的 i值是多少?如果结转相同的 i值,那么什么时候“分叉”停止?

fork()不更改 i的值,因此子进程看到的值与其父进程相同。

关于 fork()需要记住的是,它调用了一次,但是返回了两次,一次在父进程中,一次在新克隆的子进程中。

对于一个更简单的示例,请考虑以下代码:

printf("This will be printed once.\n");
fork();
printf("This will be printed twice.\n");
fork();
printf("This will be printed four times.\n");
fork();
printf("This will be printed eight times.\n");

fork()创建的子进程(几乎)是其父进程的完全克隆,因此,从它自己的角度来看,它“记得”是其父进程,继承父进程的所有状态(包括所有变量值、调用堆栈和正在执行的指令)。唯一的直接区别(除了系统元数据(如 getpid()返回的进程 ID)之外)是 fork()的返回值,它在子进程中为零,但在父进程中为非零(实际上是子进程的 ID)。

2^n - 1是否总是一种计算分叉子孩子数量的方法?那么,这里的 n=3,意思是 2^3 - 1 = 8 - 1 = 7的孩子,哪个是正确的?

每个执行 fork()的进程都会变成两个进程(除非在非常规的错误情况下,fork()可能会失败)。如果父代和子代继续执行相同的代码(即他们不检查 fork()的返回值,或者他们自己的进程 ID,并根据它分支到不同的代码路径) ,那么每个后续 fork 将使进程数量增加一倍。所以,是的,经过三次分叉之后,您最终将得到总共2 & sup3; = 8个进程。