视觉上For循环中fork()会发生什么


75

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

这是输出:

我是一个非常有远见的人,所以要真正了解事物,唯一的方法就是绘制图表。我的讲师说会有8个喜词。我编写并运行了代码,实际上有8个hi语句。但是我真的不明白。所以我画了下图:

在此处输入图片说明

该图已更新以反映评论:)

观察结果:

  1. 父进程(主进程)必须将循环迭代3次。然后调用printf
  2. 在父级for循环的每次迭代中,都会调用fork()
  3. 在每次fork()调用之后,i都会递增,因此每个孩子都会在i递增之前从其开始for循环
  4. 在每个for循环的末尾,均会打印“ hi”

这是我的问题:

  • 我的图表正确吗?
  • 为什么在输出中有两个实例i=0
  • i在fork()之后,将什么值带给每个孩子?如果i结转了相同的值,那么“分叉”何时停止?
  • 是否总是2^n - 1会这样一种方法来计算分叉的孩子数量?那么,在这里n=3,这意味着2^3 - 1 = 8 - 1 = 7孩子,哪个是正确的?

为什么不运行它并打印出iPID和父PID fork()。跟踪正在发生的事情应该很容易
基本

3
@Basic,这是我要做的第一件事。我什至使用了getpid()和getppid(),这就是为什么/为什么我相信我的图表是正确的。但我真的希望有人对此进行验证。
lucidgold

那是一个非常不错的图。您使用点/图形制作了吗?
史蒂文·卢

2
我使用了Microsoft Visio。但是我现在使用LibreOffice Draw,它非常类似于Open Office Draw,而且我相信它们都是开源项目,因此免费!
lucidgold

1
默认情况下,如何禁用缓冲?在gfg ide上,不使用fflush-ide.geeksforgeeks.org/0TWiEZ和使用fflush-ide.geeksforgeeks.org/0JKaH5
Udayraj Deshmukh 17/09/18

Answers:


44

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两次1打印,四次打印,八次2打印以及hi八次打印。


1
太好了,所以我的图表是正确的。但是,这解释了为什么有两个i = 0实例的原因(因为我总是打印i的当前值)。即使i = 0被结转,下一个fork只会在i递增时执行!谢谢!
lucidgold

4
是的,fork()只是重复该过程,而且两者都以相同的方式进行。他们俩都刚刚从fork()通话中返回,并且直到下次他们遇到与的通话时才会打另一个电话fork()。唯一的区别是fork()在子级返回0,在父级返回其他值,但是就每个进程而言,它们都只是从a返回fork(),然后继续进行。
Crowman 2014年

@PaulGriffiths根据您的解释,这将是一棵平衡的树。是不是,但是执行顺序会有所不同,因为顺序不是确定性的,而是实质上是平衡的。是不是
Naseer

@khan:我的解释并没有说明这些事情何时发生,只是发生了多少次。您是正确的,执行顺序通常是不可预测的。
Crowman

1
@khan:不知道如何详细说明答案。只是画出来。从一个节点开始,然后重复三遍,向您可以看到的每个节点添加一个子节点。您将最终得到八个节点:一个带有三个子节点,一个带有两个子节点,两个带有一个子节点,还有四个没有子节点。
Crowman

12
  1. 是的,这是正确的。(见下文)
  2. 不,在调用之后i++执行,因为这就是循环的方式。forkfor
  3. 如果一切顺利,是的。但是,请记住,这fork可能会失败。

关于第二个的一些解释:

类似于:

因此i,在分叉的过程中(父和子)都是增量之前的值。但是,增量是在之后立即执行的fork(),因此我认为该图可以视为正确。


那么,由于在fork()调用之后i递增,那么子级中的i将是父级中i的最后一个值?所以我的图不正确吗?
lucidgold

@lucidgold这就是为什么我告诉您放入printf的原因。
2501年

1
@lucidgold我认为您的图表是正确的,因为我在通话增加后立即就停止了分叉。于浩解释得很好。
2501年

1
@lucidgold:因为你printf() 以后fork()i0在父级和第一个子级中同时执行printf()。图中您缺少的是第一个红色的孩子应该有一个i == 0,但是这个盒子却没有fork()。与第二个红色孩子相同,它应该在上有一个i == 1printf(),但也不会fork()
克劳曼,2014年

1
@lucidgold:因为到创建第一个孩子时,第一个孩子fork()已经发生。第一个孩子将fork()在循环的下一次迭代时为i == 1。但是它将在执行该操作之前完成循环的第一个迭代(因为它是在该迭代的中间创建的),所以它将printf()i=0
克劳曼,2014年

4

要一一回答您的问题:

我的图表正确吗?

是的,本质上。这也是一个非常不错的图。

也就是说,如果您将i=0etc.标签解释为是指完整循环迭代,那是正确的。但是,该图显示的是,在每次调用fork()之后fork(),分叉的子进程也将执行调用之后的当前循环迭代部分。

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

因为您有printf()after fork(),所以它由父进程和刚刚分叉的子进程执行。如果将移到printf()之前fork(),它将仅由父级执行(因为子进程尚不存在)。

i结帐后,每个孩子有什么价值fork()?如果i结转了相同的值,那么“分叉”何时停止?

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

要记住的事情 fork()是,它只被调用一次,但是它返回两次-一次在父进程中,一次在新克隆的子进程中。

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

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

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

每个执行fork()转弯的流程都会变成两个流程(除非在异常错误情况下否则fork()可能会失败)。如果父级和子级继续执行相同的代码(即,他们不检查的返回值fork()或它们自己的进程ID,并根据其分支到不同的代码路径),则每个后续派生将使进程数加倍。因此,是的,经过三叉,您最终将总共获得2³= 8个过程。


极好的答案。我已经更新了图表以反映您的输入。谢谢!
lucidgold
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.