进程控制之进程等待

本篇目标:

学习进程等待并了解几个概念与函数

一.进程等待

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**。

  1. 小王先打了一个电话给小李,开口就问:"你讲完了吗?我想找你补习。"小李说:"还没呢,我还在给别人讲,至少还要半小时。"小王听完,没有握着电话一直等 ,直接说:"那你先忙,我等会儿再找你。" 然后就挂了电话。这就是 WNOHANG 的行为:问一次,没好就立刻走,不阻塞、不卡住

  2. 挂掉电话后,小王没有闲着,他回到座位上自己翻课本、看错题、做练习题,一边忙自己的事,一边等小李有空。父进程没有被卡住,而是继续执行自己的代码。

  3. 过了十几分钟,小王停下笔,再打一次电话问小李:"现在讲完了吗?"小李还是说:"还没,快了,再等会儿。"小王依旧不等待,直接挂电话,继续低头做自己的题目,完全不耽误时间。

  4. 又过了一会儿,小王第三次打电话过去,这次小李说:"终于讲完了,现在可以帮你补习了。"这时候小王才开始和小李聊补习的内容,相当于父进程检测到子进程退出,完成回收。

整个过程里,小王从来没有一直拿着电话死等 ,每次只问一下,没好就立刻去干自己的事,隔一段时间再来问一次。这就是 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;
}

下一篇:进程控制之进程替换

相关推荐
云栖梦泽2 小时前
Linux内核与驱动:13.从设备树到Platform平台总线
linux·运维·c++·嵌入式硬件
纯氧゜2 小时前
文件名长度真相:别再被8.3规则误导了
linux·ai写作
Agent产品评测局2 小时前
企业流程异常处理自动化落地,预警处置全流程实现方案:2026企业“数字免疫系统”构建指南
运维·人工智能·ai·chatgpt·自动化
xlq223222 小时前
43.线程同步
大数据·linux
charlie1145141912 小时前
嵌入式Linux驱动开发指南02——内核空间基础与硬件访问
linux·运维·c语言·驱动开发·嵌入式硬件
不会写DN2 小时前
TCP 长连接服务:登录注册认证体系实战指南
服务器·网络·网络协议·tcp/ip·计算机网络·面试
萑澈3 小时前
实践教程:我如何用 n8n 自动化“软著申请”中最头疼的文档撰写工作
运维·elasticsearch·自动化
zzzsde3 小时前
【Linux】进程信号(1)理解信号及信号产生的方式
linux·运维·服务器·算法
DBA大董3 小时前
TDengine3.x 数据文件详解
大数据·linux·时序数据库·dba·tdengine