1 概述
我们知道一次wait或waitpid调用只能清理一个子进程,所以清理多个子进程应使用循环。并且即使你参1传的是-1,它的含义代表回收所有父进程的子进程,但是每次都是一个个回收即多次回收多个子进程,并非一次回收所有子进程。
2 代码例子
1)循环回收多个子进程。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
int i;
pid_t pid, wpid,tmpid;//tmpid
for(i = 0; i < 5; i++) {
pid = fork();
if(pid == 0) {
break;
}
}
if(5 == i){
while(wpid = waitpid(-1,NULL,0)){//阻塞形式循环回收
if(wpid > 0){
printf("waitpid child pid = %d\n", wpid);//打印回收的子进程
}
else if(wpid == 0){//实际上阻塞回收的话这一步不会执行,因为子进程肯定执行完了父进程才会进,并且进>0那里
sleep(1);
printf("111\n");
continue;
}
else{
printf("Not can waitpid child,return value = %d.\n",wpid);//返回-1说明回收出错即没有可回收的子进程
break;
}
}
}
else {
sleep(i);//子进程按序输出
printf("I'm %dth child, pid = %d, gpid=%d\n", i+1, getpid(), getgid());
}
return 0;
}
结果,可以看到,每个子进程间隔的被父进程回收,然后没有子进程可回收时返回-1退出while。
造成间隔回收子进程的原因分析:
在父进程中,我们循环fork了五个子进程,理应来说父进程由于循环for可能慢点执行,但是由于子进程存在sleep(i)秒,所以与睡眠相比,父进程for循环消耗时间是非常少的,所以父进程一早就在waitpid等待子进程睡眠结束回收了,所以就造成每当一有子进程结束,父进程就可回收的间隔现象了。
实际上你可以把子进程的睡眠注释掉,可以看到子进程结束非常快,父进程速度慢而回收不够快只能最后慢慢回收了。看图二。
图二如下:
2)非阻塞回收多个子进程。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
int i;
pid_t pid, wpid,tmpid;//tmpid
for(i = 0; i < 5; i++) {
pid = fork();
if(pid == 0) {
break;
}
}
if(5 == i){
/*
while(wpid = waitpid(-1,NULL,0)){//阻塞形式循环回收
if(wpid > 0){
printf("waitpid child pid = %d\n", wpid);//打印回收的子进程
}
else if(wpid == 0){//实际上阻塞回收的话这一步不会执行,因为子进程肯定执行完了父进程才会进,并且进>0那里
sleep(1);
printf("111\n");
continue;
}
else{
printf("Not can waitpid child,return value = %d.\n",wpid);//返回-1说明回收出错即没有可回收的子进程
break;
}
}
*/
while((wpid = waitpid(-1,NULL,WNOHANG)) != -1){//非阻塞形式循环回收,必须在while判断且必须用括号括起运算表达式
if(wpid > 0){
printf("waitpid child pid = %d\n", wpid);//打印回收的子进程
}
else if(wpid == 0){//非阻塞回收,父进程速度快,子进程极大可能未结束,所以返回0,让父进程继续回收即可
sleep(1);
printf("Continue waitpid.\n");
continue;
}
//else{
//printf("Not can waitpid child,return value = %d.\n",wpid);//返回-1说明回收出错即没有可回收的子进程,但若还有子进程存活的话这里不会执行,因为上面continue了
//break;
//}
}
}
else {
sleep(i);//子进程按序输出
printf("I'm %dth child, pid = %d, gpid=%d\n", i+1, getpid(), getgid());
}
return 0;
}
结果可以看到,可以正确回收五个子进程。
非阻塞回收的错误写法1):
返回值没有在while判断:
while((wpid = waitpid(-1,NULL,WNOHANG))){//非阻塞形式循环回收,不在while判断导致父进程没有可回收的子进程时返回0,while直接退出
printf("wpid = %d\n",wpid);//可以看到这行并没有并打印
if(wpid > 0){
printf("waitpid child pid = %d\n", wpid);//打印回收的子进程
}
else if(wpid == 0){//非阻塞回收,父进程速度快,子进程极大可能未结束,所以返回0,让父进程继续回收即可
sleep(1);
printf("Continue waitpid.\n");
continue;
}
else{
printf("Not can waitpid child,return value = %d.\n",wpid);//返回-1说明回收出错即没有可回收的子进程,但若还有子进程存活的话这里不会执行,因为上面continue了
break;
}
}
判断必须在while中判断,因为非阻塞时,父进程执行非常快而子进程没有结束,导致wpid返回的是0而直接退出了while循环,在while打印数据可以看到并没有输出任何内容,所以父进程直接返回了并且在终端中第一时间打印出来。
看第二行,由于父进程直接退出while导致终端描述符第一个被打印出来。进而导致最后一行出现伪死状态即让人以外卡主。
所以循环回收多个子进程时判断必须在while括号中执行。
非阻塞回收的错误写法2):
在while循环中判断,没有用括号将运算表达式用括起:
while(wpid = waitpid(-1,NULL,WNOHANG) != -1){//非阻塞形式循环回收,必须用括号括起运算表达式
if(wpid > 0){
printf("waitpid child pid = %d\n", wpid);//打印回收的子进程
}
else if(wpid == 0){//非阻塞回收,父进程速度快,子进程极大可能未结束,所以返回0,让父进程继续回收即可
sleep(1);
printf("Continue waitpid.\n");
continue;
}
//else{
//printf("Not can waitpid child,return value = %d.\n",wpid);//返回-1说明回收出错即没有可回收的子进程,但若还有子进程存活的话这里不会执行,因为上面continue了
//break;
//}
}
在while循环中判断,必须给整个运算表达式用括号括起来,否则gcc认为它一直为真值(可加上-Wall获取警告)即wpid=1,导致无限循环打印wpid>0的内容,即下图wpid = 1那行内容。
3 总结
循环回收多个子进程时,必须在while中判断且要用括号括起整个运算表达式。