进程控制之进程等待

张开发
2026/4/18 23:47:01 15 分钟阅读

分享文章

进程控制之进程等待
本篇目标学习进程等待并了解几个概念与函数一.进程等待1.进程等待必要性之前讲过子进程退出⽗进程如果不管不顾就可能造成僵尸进程的问题进而造成内存泄漏。• 另外进程⼀旦变成僵尸状态那就刀枪不入杀⼈不眨眼的kill-9也无能为力因为谁也没有办法杀死一个已经死去的进程。• 最后父进程派给子进程的任务完成的如何我们需要知道。如果⼦进程运⾏完成结果对还是 不对或者是否正常退出。• 父进程通过进程等待的方式回收子进程资源获取子进程退出信息例如#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先看waitstatus其实是个输出型参数但是下面讲waitpid时在讲他等下我们设置为NULL而返回值则是成功返回被等待进程 pid 失败返回-1 可以等待任意个退出的子进程。#includestdio.h #includeerrno.h #includestdlib.h #includestring.h #includeunistd.h #includesys/types.h #includesys/wait.h int main() { pid_t idfork(); if(id0) { int cnt5; while(cnt) { printf(我是一个子进程pid:%d,ppid:%d\n,getpid(),getppid()); sleep(1); cnt--; } //子进程退出 exit(0); } pid_t ridwait(NULL); if(rid0) { printf(我是一个父进程pid:%d\n,getpid()); printf(等待成功,rid:%d\n,rid); } return 0; }输出结果可以看出返回值确实是子进程的pid现在有一个问题如果子进程没有退出父进程却wait呢其实结果与这个差不多// 子进程代码 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的那个子进程下面演示一下#includestdio.h #includeerrno.h #includestdlib.h #includestring.h #includeunistd.h #includesys/types.h #includesys/wait.h int main() { pid_t idfork(); if(id0) { int cnt5; while(cnt) { printf(我是一个子进程pid:%d,ppid:%d\n,getpid(),getppid()); sleep(1); cnt--; } //子进程退出 exit(0); } pid_t ridwaitpid(id,NULL,0); if(rid0) { printf(我是一个父进程pid:%d\n,getpid()); printf(等待成功,rid:%d\n,rid); } return 0; }输出结果目前与wait的差不多2.其次是第二个参数该参数是⼀个输出型参数负责由操作系统填充而父进程通过status拿到子进程退出时的详细信息也包括退出码演示一下#includestdio.h #includeerrno.h #includestdlib.h #includestring.h #includeunistd.h #includesys/types.h #includesys/wait.h int main() { pid_t idfork(); if(id0) { int cnt5; while(cnt) { printf(我是一个子进程pid:%d,ppid:%d\n,getpid(),getppid()); sleep(1); cnt--; } //子进程退出 exit(1); } int status0; pid_t ridwaitpid(id,status,0); if(rid0) { 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想要获取退出码也很简单如图所示即可printf(我是一个父进程pid:%d,status:%d\n,getpid(),(status8)0xFF);输出结果3.第三个参数options:默认为 0 表示阻塞等待这里用个例子来加深我们对阻塞等待的理解期末小王找小李补高数拨通电话后小李说自己正在给别人补英语要半小时才能收尾。小王选择不挂电话也不做看书、刷手机这些事就举着电话一动不动干等—— 这就是阻塞等待的核心。这半小时里小李正常补他的课子进程正常运行没到退出节点小王就一直卡在等待状态啥后续的事都做不了。直到小李补完课、彻底腾出身子进程执行exit/return 0退出小王才被唤醒开始请教问题。如果小李永远忙不完子进程死循环、永不退出小王就会永远卡在这干等彻底卡死。其实option除了0还有其他的今天讲一个有关waitpid的如图所示WNOHANG其实就是非阻塞等待依旧举一个例子期末快到了小王数学很差想找学霸小李帮忙补习功课他知道小李现在正在给别的同学讲题还没结束子进程没 exit /return。小王这次不想一直拿着电话死等他选择非阻塞等待也就是带上WNOHANG。小王先打了一个电话给小李开口就问“你讲完了吗我想找你补习。”小李说“还没呢我还在给别人讲至少还要半小时。”小王听完没有握着电话一直等直接说“那你先忙我等会儿再找你。” 然后就挂了电话。这就是WNOHANG的行为问一次没好就立刻走不阻塞、不卡住。挂掉电话后小王没有闲着他回到座位上自己翻课本、看错题、做练习题一边忙自己的事一边等小李有空。父进程没有被卡住而是继续执行自己的代码。过了十几分钟小王停下笔再打一次电话问小李“现在讲完了吗”小李还是说“还没快了再等会儿。”小王依旧不等待直接挂电话继续低头做自己的题目完全不耽误时间。又过了一会儿小王第三次打电话过去这次小李说“终于讲完了现在可以帮你补习了。”这时候小王才开始和小李聊补习的内容相当于父进程检测到子进程退出完成回收。整个过程里小王从来没有一直拿着电话死等每次只问一下没好就立刻去干自己的事隔一段时间再来问一次。这就是waitpid(..., WNOHANG)的非阻塞等待子进程没退出 → 立刻返回父进程继续运行子进程退出了 → 再处理回收。有了例子后再谈谈返回值此时的pid_t idwaitpid(id,status,WNOHAGN);如果id0则等待结束id0调用结束但是子进程没有结束id0则失效了下面以代码演示#includestdio.h #includeerrno.h #includestdlib.h #includestring.h #includeunistd.h #includesys/types.h #includesys/wait.h int main() { pid_t idfork(); if(id0) { int cnt5; while(cnt) { printf(我是一个子进程pid:%d,ppid:%d\n,getpid(),getppid()); sleep(1); cnt--; } //子进程退出 exit(1); } while(1) { int status0; pid_t ridwaitpid(id,status,WNOHANG); if(rid0) { printf(我是一个父进程pid:%d,status:%d\n,getpid(),(status8)0xFF); printf(等待成功,rid:%d\n,rid); break; } else if(rid0) { printf(本轮调用结束但是子进程没有退出\n); sleep(1); } else { printf(等待失效\n); break; } } return 0; }输出结果可以看出父进程一直在询问子进程是否结束直到子进程exit其实非阻塞等待的一个重要作用就是让等待方可以做自己的事情如代码所示#includestdio.h #includeerrno.h #includestdlib.h #includestring.h #includeunistd.h #includesys/types.h #includesys/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 idfork(); if(id0) { int cnt5; while(cnt) { printf(我是一个子进程pid:%d,ppid:%d\n,getpid(),getppid()); sleep(1); cnt--; } exit(1); } // 父进程非阻塞等待 执行自定义函数 while(1) { int status0; pid_t ridwaitpid(id,status,WNOHANG); if(rid0) { printf(父进程[%d]: 回收子进程成功退出码%d\n,getpid(),(status8)0xFF); break; } else if(rid0) { do_parent_business(); sleep(1); } else { printf(等待失效\n); break; } } return 0; }输出结果3.补充内容1.WEXITSTATUS(status): 若 WIFEXITED 非零提取子进程退出码。查看进程的退出码如代码所示#includestdio.h #includeerrno.h #includestdlib.h #includestring.h #includeunistd.h #includesys/types.h #includesys/wait.h int main() { pid_t idfork(); if(id0) { int cnt5; while(cnt) { printf(我是一个子进程pid:%d,ppid:%d\n,getpid(),getppid()); sleep(1); cnt--; } //子进程退出 exit(1); } int status0; pid_t ridwaitpid(id,status,0); if(rid0) { printf(我是一个父进程pid:%d,status:%d\n,getpid(),WEXITSTATUS(status)); printf(等待成功,rid:%d\n,rid); } return 0; }2.WIFEXITED(status): 若为正常终止子进程返回的状态则为真查看进程 是否是正常退出如代码所示#includestdio.h #includeerrno.h #includestdlib.h #includestring.h #includeunistd.h #includesys/types.h #includesys/wait.h int main() { pid_t idfork(); if(id0) { int cnt5; while(cnt) { printf(我是一个子进程pid:%d,ppid:%d\n,getpid(),getppid()); sleep(1); cnt--; } //子进程退出 exit(1); } int status0; pid_t ridwaitpid(id,status,0); if(rid0) { if(WIFEXITED(status)) { printf(我是一个父进程pid:%d,status:%d\n,getpid(),WEXITSTATUS(status)); printf(等待成功,rid:%d\n,rid); } else printf(退出异常\n); } return 0; }下一篇进程控制之进程替换

更多文章