在下面的例子中如何等待每个进程终止?
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 */
程序在从 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 */