本篇目标:
学习进程等待并了解几个概念与函数
一.进程等待
1.进程等待必要性
之前讲过,子进程退出,⽗进程如果不管不顾,就可能造成僵尸进程的问题,进而造成内存泄漏。
• 另外,进程⼀旦变成僵尸状态,那就刀枪不入,杀⼈不眨眼的kill-9也无能为力,因为谁也没有办法杀死一个已经死去的进程。
• 最后,父进程派给子进程的任务完成的如何,我们需要知道。如果⼦进程运⾏完成,结果对还是 不对,或者是否正常退出。
• 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
例如:
cpp
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
int main()
{
pid_t pid;
if ((pid = fork()) == 0)
{
// 子进程执行逻辑
printf("子进程,PID: %d,PPID:%d, 开始运行\n", getpid(),getppid ());
sleep(2);
printf("子进程,PID: %d,PPID:%d, 运行结束\n", getpid(),getppid ());
//子进程退出
exit(0);
}
printf("父进程,PID: %d 开始等待子进程\n", getpid());
return 0;
}
此时子进程的代码与数据均已经销毁了,但是子进程的退出信息父进程却没有去拿,就导致子进程一直处于僵尸进程
2.进程等待的方法
2.1.wait方法
如图:

我们先不看waitpid,先看wait,status其实是个输出型参数,但是下面讲waitpid时,在讲他,等下我们设置为NULL,而返回值则是成功返回被等待进程 pid ,失败返回-1 ,可以等待任意个退出的子进程。
cpp
#include<stdio.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid_t id=fork();
if(id==0)
{
int cnt=5;
while(cnt)
{
printf("我是一个子进程,pid:%d,ppid:%d\n",getpid(),getppid());
sleep(1);
cnt--;
}
//子进程退出
exit(0);
}
pid_t rid=wait(NULL);
if(rid>0)
{
printf("我是一个父进程,pid:%d\n",getpid());
printf("等待成功,rid:%d\n",rid);
}
return 0;
}
输出结果:
可以看出返回值确实是子进程的pid,现在有一个问题:如果子进程没有退出,父进程却wait呢?
其实结果与这个差不多
cpp
// 子进程代码
while (1)
{
printf("子进程还在死循环中...\n");
sleep(1);
}
// 永远跑不到这里的 exit(0);
此时父进程如果调用 wait(NULL),会发生什么?
父进程执行到 wait(NULL),发现子进程还在跑 while(1),根本没退出。
父进程直接暂停执行(阻塞),停在 wait(NULL) 这一行,不再往下走。
子进程依然在欢快地跑死循环,打印日志。
父进程就这么一直卡着,永远等不到头,除非你手动杀掉子进程(比如用 kill 命令)。
2.2.waitpid方法
如图所示:

<1>.先关注它的第一个参数,如图所示:

重点关注-1与>0,其实-1表示等待任意的子进程,而>0则表示等待id的那个子进程,下面演示一下:
cpp
#include<stdio.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid_t id=fork();
if(id==0)
{
int cnt=5;
while(cnt)
{
printf("我是一个子进程,pid:%d,ppid:%d\n",getpid(),getppid());
sleep(1);
cnt--;
}
//子进程退出
exit(0);
}
pid_t rid=waitpid(id,NULL,0);
if(rid>0)
{
printf("我是一个父进程,pid:%d\n",getpid());
printf("等待成功,rid:%d\n",rid);
}
return 0;
}
输出结果:
目前与wait的差不多
<2>.其次是第二个参数,,该参数是⼀个输出型参数,负责由操作系统填充,而父进程通过status拿到子进程退出时的详细信息,也包括退出码,演示一下:
cpp
#include<stdio.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid_t id=fork();
if(id==0)
{
int cnt=5;
while(cnt)
{
printf("我是一个子进程,pid:%d,ppid:%d\n",getpid(),getppid());
sleep(1);
cnt--;
}
//子进程退出
exit(1);
}
int status=0;
pid_t rid=waitpid(id,&status,0);
if(rid>0)
{
printf("我是一个父进程,pid:%d,status:%d\n",getpid(),status);
printf("等待成功,rid:%d\n",rid);
}
return 0;
}
输出结果:
此时可能就会有人疑惑:为什么status不是退出码1呢?这256是如何来的呢?
其实status的真正的二进制应该是这样的:
前16位不用管,全为0,后16位中的前8位才是退出码,后8位,如果退出正常的话则为0,所以我们的退出码的二进制在后16位中的前8位为00000001,而后面又全为0,就导致结果为256,想要获取退出码也很简单,如图所示即可:
cpp
printf("我是一个父进程,pid:%d,status:%d\n",getpid(),(status>>8)&0xFF);
输出结果:
<3>.第三个参数 ,options:默认为 0 ,表示阻塞等待,这里用个例子来加深我们对阻塞等待的理解:
期末小王找小李补高数,拨通电话后,小李说自己正在给别人补英语,要半小时才能收尾。
小王选择不挂电话,也不做看书、刷手机这些事,就举着电话一动不动干等------ 这就是阻塞等待的核心。
这半小时里,小李正常补他的课(子进程正常运行,没到退出节点),小王就一直卡在等待状态,啥后续的事都做不了。
直到小李补完课、彻底腾出身(子进程执行exit/return 0退出),小王才被唤醒,开始请教问题。
如果小李永远忙不完(子进程死循环、永不退出),小王就会永远卡在这干等,彻底卡死。
其实option除了0,还有其他的,今天讲一个有关waitpid的,如图所示:

WNOHANG其实就是非阻塞等待,依旧举一个例子:
期末快到了,小王数学很差,想找学霸小李帮忙补习功课,他知道小李现在正在给别的同学讲题,还没结束(子进程没 exit /return)。
小王这次不想一直拿着电话死等,他选择非阻塞等待 ,也就是带上**WNOHANG**。
-
小王先打了一个电话给小李,开口就问:"你讲完了吗?我想找你补习。"小李说:"还没呢,我还在给别人讲,至少还要半小时。"小王听完,没有握着电话一直等 ,直接说:"那你先忙,我等会儿再找你。" 然后就挂了电话。这就是
WNOHANG的行为:问一次,没好就立刻走,不阻塞、不卡住。 -
挂掉电话后,小王没有闲着,他回到座位上自己翻课本、看错题、做练习题,一边忙自己的事,一边等小李有空。父进程没有被卡住,而是继续执行自己的代码。
-
过了十几分钟,小王停下笔,再打一次电话问小李:"现在讲完了吗?"小李还是说:"还没,快了,再等会儿。"小王依旧不等待,直接挂电话,继续低头做自己的题目,完全不耽误时间。
-
又过了一会儿,小王第三次打电话过去,这次小李说:"终于讲完了,现在可以帮你补习了。"这时候小王才开始和小李聊补习的内容,相当于父进程检测到子进程退出,完成回收。
整个过程里,小王从来没有一直拿着电话死等 ,每次只问一下,没好就立刻去干自己的事,隔一段时间再来问一次。这就是 waitpid(..., WNOHANG) 的非阻塞等待:子进程没退出 → 立刻返回,父进程继续运行;子进程退出了 → 再处理回收。
有了例子后,再谈谈返回值,此时的pid_t id=waitpid(id,&status,WNOHAGN);如果id>0则等待结束;id==0,调用结束,但是子进程没有结束;id<0,则失效了,下面以代码演示:
cpp
#include<stdio.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid_t id=fork();
if(id==0)
{
int cnt=5;
while(cnt)
{
printf("我是一个子进程,pid:%d,ppid:%d\n",getpid(),getppid());
sleep(1);
cnt--;
}
//子进程退出
exit(1);
}
while(1)
{
int status=0;
pid_t rid=waitpid(id,&status,WNOHANG);
if(rid>0)
{
printf("我是一个父进程,pid:%d,status:%d\n",getpid(),(status>>8)&0xFF);
printf("等待成功,rid:%d\n",rid);
break;
}
else if(rid==0)
{
printf("本轮调用结束,但是子进程没有退出\n");
sleep(1);
}
else
{
printf("等待失效\n");
break;
}
}
return 0;
}
输出结果:
可以看出父进程一直在询问子进程是否结束,直到子进程exit
其实非阻塞等待的一个重要作用就是让等待方可以做自己的事情,如代码所示:
cpp
#include<stdio.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
// 1. 数学计算函数:计算1~n的累加和
int calculate_sum(int n) {
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
return sum;
}
// 2. 轮询计数函数:统计父进程检查子进程的次数(静态变量持久化)
void check_counter() {
static int count = 0;
count++;
}
// 3. 父进程核心工作函数:整合所有任务
void do_parent_business() {
printf("结果为:%d\n",calculate_sum(100));
check_counter();
}
int main()
{
pid_t id=fork();
if(id==0)
{
int cnt=5;
while(cnt)
{
printf("我是一个子进程,pid:%d,ppid:%d\n",getpid(),getppid());
sleep(1);
cnt--;
}
exit(1);
}
// 父进程:非阻塞等待 + 执行自定义函数
while(1)
{
int status=0;
pid_t rid=waitpid(id,&status,WNOHANG);
if(rid>0)
{
printf("父进程[%d]: 回收子进程成功,退出码:%d\n",getpid(),(status>>8)&0xFF);
break;
}
else if(rid==0)
{
do_parent_business();
sleep(1);
}
else
{
printf("等待失效\n");
break;
}
}
return 0;
}
输出结果:
3.补充内容
<1>.WEXITSTATUS(status): 若 WIFEXITED 非零,提取子进程退出码。(查看进程的退出码)
如代码所示:
cpp
#include<stdio.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid_t id=fork();
if(id==0)
{
int cnt=5;
while(cnt)
{
printf("我是一个子进程,pid:%d,ppid:%d\n",getpid(),getppid());
sleep(1);
cnt--;
}
//子进程退出
exit(1);
}
int status=0;
pid_t rid=waitpid(id,&status,0);
if(rid>0)
{
printf("我是一个父进程,pid:%d,status:%d\n",getpid(),WEXITSTATUS(status));
printf("等待成功,rid:%d\n",rid);
}
return 0;
}
<2>.WIFEXITED(status): 若为正常终止,子进程返回的状态,则为真(查看进程 是否是正常退出)
如代码所示:
cpp
#include<stdio.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid_t id=fork();
if(id==0)
{
int cnt=5;
while(cnt)
{
printf("我是一个子进程,pid:%d,ppid:%d\n",getpid(),getppid());
sleep(1);
cnt--;
}
//子进程退出
exit(1);
}
int status=0;
pid_t rid=waitpid(id,&status,0);
if(rid>0)
{
if(WIFEXITED(status))
{
printf("我是一个父进程,pid:%d,status:%d\n",getpid(),WEXITSTATUS(status));
printf("等待成功,rid:%d\n",rid);
}
else
printf("退出异常\n");
}
return 0;
}
下一篇:进程控制之进程替换