在下面的例子中如何等待每个进程终止?

How to wait for each process to terminate in the following example?

程序在从 scanf 中获取 4 或 5 个值后终止。 但我希望它接受 8 个值(总共 8 个进程)然后终止。

void main() {
    fork();
    fork();
    fork();

    scanf("%d",&j);
    printf("The values are %d\n",j);

    wait(0);
}

这里的问题是 wait(0) 只等待第一个 child。主进程一旦成功获得自己的输入就会退出,直接 child 也完成了同样的操作。

最简单的拼凑就是等到没有更多的 children 等待:

void main() {
    int j;
    fork();
    fork();
    fork();

    scanf("%d", &j);
    printf("The values are %d\n",j);

    while (wait(0) != -1);
}

这会按预期从交互式终端读取 8 个值。

但是你不应该在真正的程序中这样做。主线程应该改为读取所有 8 个值并根据需要创建 children 来处理它们。不这样做会导致竞争条件和缓冲问题。

这是一个例子,为简洁起见少用了一个 fork()

$ for i in {1..4}; do echo "$i"; done  | ./foo
The values are 1
The values are 0
The values are 0
The values are 0

$ for i in {1..4}; do echo "$i"; sleep 0.1; done  | ./foo
The values are 1
The values are 2
The values are 3
The values are 4

在前一种情况下,对管道的多次写入变成一次读取,由任意进程拾取。其他人一无所获。

在第二种情况下,添加了短暂的休眠以允许每次读取之间有更多时间,现在他们获得了预期的值。

您需要等待所有 children,您的 children 需要等待他们的 children,等等。目前您的 wait(0) 呼叫仅等待一个 child 改变状态。

// wait(0); 
while (1) {
   int status;
   pid_t done = wait( &status );
   if ( done == -1 )
      break; // no more children
}

那么,您必须自己回答的第一个问题是 您认为那里有多少进程? 因为您不使用 fork(2) 系统调用,每次fork执行后,你不知道你是parent还是child。

你将在故事的开头有一个进程,它执行 fork(2) 系统调用并转换为 两个 进程,然后执行(两者, parent 和 child) 第二个 fork(2) 调用,在第二次调用 returns 时,在两个进程中(每个)转换(另外两个,总共四个)。然后是第三个,它再次复制了进程的数量,所以你会在故事的结尾得到 8 运行 个进程在二叉树执行历史层次结构中完全填充到高度三。

但进程关系不一样

每个进程可以 wait(2) 为每个 拥有 children,但是 有一些进程,自成立以来 已经取得了三个、两个、一个或根本没有 fork()。根节点是唯一生成三个 fork()s 的节点,因此它最多可以执行三个 wait(2)s 而不会出错(对于它的每个 children),它的第一个 children,只做两个,第二个只做一个...像这样:

proc[0]---fork()-----fork()---fork()----exit();
          |          |        |
          |          |        proc[3]---exit();
          |          |       
          |          proc[2]--fork()----exit();
          |                   |
          |                   proc[4]---exit();
          |
          proc[1]----fork()---fork()----exit();
                     |        |
                     |        proc[5]---exit();
                     |
                     proc[6]--fork()----exit();
                              |
                              proc[7]---exit();

所以

  • proc[0] 可以 wait(2)proc[1]proc[2]proc[3];
  • proc[1] 可以 wait(2)proc[5]proc[6];
  • proc[2]只能wait(2)proc[4]
  • proc[3]不能wait(2)(调用wait(2)会报错);
  • proc[4] 不能 wait(2);
  • proc[5] 不能 wait(2);
  • proc[6]只能wait(2)proc[7]
  • proc[7] 不能 wait(2).

As wait(2) can only wait for one of such children (children 必须用fork创建,否则调用会出错) 你必须发出as许多 wait(2) 呼叫作为 fork(2) 你已经发出 ,等待他们全部,所以你必须控制你有 children 的数量(如您所见,每个进程的这个数字都不同)。例如,您可以在 parent 中递增计数器 (从 fork(2) 接收 0 结果的进程,因此您知道fork(2)你到目前为止已经发布了

int main()
{
    int forks_made = 0;
    if (fork() > 0) forks_made++;
    if (fork() > 0) forks_made++;
    if (fork() > 0) forks_made++;
    for (i = 0; i < forks_made; i++) wait(NULL);
    exit(0);
}

或者简单地说,您可以 wait(2) 直到系统调用导致错误(您没有更多 children)

int main()
{
    fork();
    fork();
    fork();
    while(wait(NULL) == 0) 
        continue;
    exit(0);
}

请注意,进程层次结构与二叉 历史记录 树不同。进程层次结构是这样的:

proc[0]
|
+--proc[1]
|  |
|  +--proc[5]
|  |
|  `--proc[6]
|     |
|     `--proc[7]
|
+--proc[2]
|  |
|  `--proc[4]
|
`--proc[3]

假设我写了下面的代码:

int main()
{
    fork(); fork(); fork();
    wait(0); wait(0); wait(0);
    exit(0);
}

结果是:

                                ok             ok         ok
p[0]-f()-f()-f()----------------w()------------w()--------w()-exit();
     |   |   |                  ^              ^          ^
     |   |   |      err err err |              |          |
     |   |   +-p[3]-w()-w()-w()-exit();        |          |
     |   |                                     |          |
     |   |                         ok  err err |          |
     |   +-p[2]-f()----------------w()-w()-w()-exit();    |
     |          |                  ^                      |
     |          |                  |                      |
     |          |      err err err |                      |
     |          +-p[4]-w()-w()-w()-exit();                |
     |                             ok             ok  err |
     +-p[1]-f()-f()----------------w()------------w()-w()-exit();
            |   |                  ^              ^
            |   |      err err err |              |
            |   +-p[5]-w()-w()-w()-exit();        |
            |                         ok  err err |
            +-p[6]-f()----------------w()-w()-w()-exit();
                   |                  ^
                   |      err err err |
                   +-p[7]-w()-w()-w()-exit();

注意

即使child人死之前 parent做了一个wait(2)内核在进程中维护它们table (但没有分配任何资源)作为 僵尸进程 只是为了等待 parent 执行正确的 wait(2) 系统调用。这就是为什么内核知道你可以做一个 wait(2) 或不可以(你只有做了 fork(2) 才能等待)。

注 2

为什么过程只有 read(2) 的部分结果和完成?好吧,我一直在阅读一些文档并进行一些测试,并且在我测试过的三个操作系统上的行为是不同的:

  • MacOS X。我已经测试过,当进程组组长 exit(2)s 时,它的所有 children 都从 read(2) 调用中唤醒并给出 ETIMEDOUT 错误(令人惊讶)
  • FreeBSD。在 FreeBSD 上,结果相似,但错误不同 (EIO)。
  • Linux。在 Linux 上,终端驱动程序在原始模式下只给每个进程一个输入字符(即使有更多)(它们在读取时输出每个字符)这是迄今为止最奇怪的行为。

由于正常的作业控制使得shell重新获取控制终端并且进程组不再是终端设备的控制组,终端应该唤醒所有试图read(2)的进程它带有错误代码,所以也许 FreeBSD 是我们应该得到的最连贯的结果。

用于测试此案例的代码如下:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>

void hndlr(int sig)
{
    printf("%d(prnt=%d,pgrp=%d): signal %d received\n",
            getpid(), getppid(), getpgrp(), sig);
}

int main()
{
    int i;
    char line[1024];

    for (i = 1; i < 64; i++)
        signal(i, hndlr);
    printf("pid=%d, pgrp=%d\n", getpid(), getpgrp());
    fork();
    fork();
    fork();
    i = read(0, line, sizeof line);
    switch(i) {
    case -1:
        printf("pid=%d, prnt=%d, pgrp=%d: read: %s(errno=%d)\n",
                getpid(), getppid(), getpgrp(), strerror(errno), errno);
        break;
    case 0:
        printf("pid=%d, prnt=%d, pgrp=%d: read: EOF\n",
                getpid(), getppid(), getpgrp());
        break;
    default:
        printf("pid=%d, prnt=%d, pgrp=%d: read: [%*.*s]\n",
                getpid(), getppid(), getpgrp(), i, i, line);
        break;
    }
#if 0
    wait(NULL);
    wait(NULL);
    wait(NULL);
#endif
} /* main */