Linux进程等待

目录

一、进程等待的原因

二、进程等待的方法

(一)wait方法

1. 定义

2. 进程等待能回收子进程的僵尸状态(wait方法验证)

3. 子进程没退出时,父进程在wait上进行阻塞等待,直到子进程僵尸,wait自动回收(wait) 

(二)waitpid方法

(三)获取子进程status

1. status结构

2. 我们不能对status整体使用

3. 正确使用

(四)图示说明父进程等待子进程的方式

(五)阻塞等待和非阻塞等待


一、进程等待的原因

  • 解决子进程僵尸问题带来的内存泄露的问题
  • 父进程为什么要创建子进程?要让子进程来完成任务。子进程任务完成的如何,父进程要不要知道?要知道(需要通过进程等待的方式),获取子进程退出的信息----两个数字(退出信号和退出码)----不是必须,但是系统需要提供这样的基础功能

二、进程等待的方法

(一)wait方法

1. 定义

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值:
成功返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

2. 进程等待能回收子进程的僵尸状态(wait方法验证)

#include<stdio.h>    
#include<stdlib.h>//exit的头文件    
#include<unistd.h>//fork头文件    
#include<sys/types.h>//pid_t类型的头文件    
#include<sys/wait.h>//wait头文件    
    
void Worker()    
{    
    int cnt = 3;    
    while(cnt)    
    {    
        printf("I am child process, pid: %d, ppid: %d, cnt: %d\n",getpid(),getppid(),cnt--);    
        sleep(1);    
    }    
}    
int main()    
{    
    pid_t id = fork();    
    if(id == 0)//子进程    
    {    
        Worker();    
        exit(0);    
    }    
    else    
    {//父进程    
        sleep(5);    
        pid_t rid = wait(NULL);    
        if(rid == id)    
        {    
            printf("wait a process,pid: %d\n",getpid());    
        }    
        sleep(5);    
    }    
    return 0;    
} 

3. 子进程没退出时,父进程在wait上进行阻塞等待,直到子进程僵尸,wait自动回收(wait) 

#include<stdio.h>    
#include<stdlib.h>//exit的头文件    
#include<unistd.h>//fork头文件    
#include<sys/types.h>//pid_t类型的头文件    
#include<sys/wait.h>//wait头文件    
    
void Worker()    
{    
    int cnt = 3;    
    while(cnt)    
    {    
        printf("I am child process, pid: %d, ppid: %d, cnt: %d\n",getpid(),getppid(),cnt--);    
        sleep(1);    
    }    
}    
int main()    
{    
    pid_t id = fork();    
    if(id == 0)//子进程    
    {    
        Worker();    
        exit(0);    
    }    
    else    
    {//父进程    
        printf("wait before!\n");//回收之前    
        pid_t rid = wait(NULL);    
        printf("wait after!\n");//回收之后    
        if(rid == id)    
        {    
            printf("wait a process,pid: %d\n",getpid());    
        }    
        sleep(5);    
    }    
    return 0;    
} 

(二)waitpid方法

pid_ t waitpid(pid_t pid, int *status, int options);
  • 返回值:当正常返回的时候waitpid返回收集到的子进程的进程ID;如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在; 
  • pid 参数指定了要等待的子进程的进程ID。它可以取以下几个值:
    • -1:等待任意一个子进程,类似于 wait 函数。
    • 大于 0:等待指定进程ID的子进程。
  • status 是一个指向整型的指针,用于接收子进程的退出状态信息。如果子进程正常结束,会将退出状态信息存放在这个指针指向的位置。
  • status 的内容会被填充为一个整数,其中包含了子进程的退出状态信息。可以通过宏来解析这个整数,比如:
    • WIFEXITED(status):若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
    • WEXITSTATUS(status):若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
  • options 是一个整型参数,可以用来指定一些选项,比如 WNOHANG 可以使 waitpid 变为非阻塞调用。
  1. 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
  2. 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
  3. 如果不存在该子进程,则立即出错返回。

(三)获取子进程status

1. status结构

  • wait和waitpid,都有一个status参数,该参数是一个输出型参数由操作系统填充
  • 如果传递NULL,表示不关心子进程的退出状态信息
  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
  • status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图 

2. 我们不能对status整体使用

#include<stdio.h>
#include<stdlib.h>//exit的头文件
#include<unistd.h>//fork头文件
#include<sys/types.h>//pid_t类型的头文件
#include<sys/wait.h>//wait头文件

void Worker()
{
    int cnt = 3;
    while(cnt)
    {
        printf("I am child process, pid: %d, ppid: %d, cnt: %d\n",getpid(),getppid(),cnt--);
        sleep(1);
    }
}
int main()
{
    pid_t id = fork();
    if(id == 0)//子进程
    {
        Worker();
        exit(10);
    }
    else
    {//父进程
        printf("wait before!\n");//回收之前
        int status;
        pid_t rid = waitpid(id, &status,0);
        printf("wait after!\n");//回收之后
        if(rid == id)
        {                                                                                                     
            printf("wait a process,pid: %d, status: %d\n",getpid(),status);
        }
        sleep(5);
    }
    return 0;
}

3. 正确使用

#include<stdio.h>    
#include<stdlib.h>//exit的头文件    
#include<unistd.h>//fork头文件    
#include<sys/types.h>//pid_t类型的头文件    
#include<sys/wait.h>//wait头文件    
    
void Worker()    
{    
    int cnt = 3;    
    while(cnt)    
    {    
        printf("I am child process, pid: %d, ppid: %d, cnt: %d\n",getpid(),getppid(),cnt--);    
        sleep(1);    
    }    
}    
int main()    
{    
    pid_t id = fork();    
    if(id == 0)//子进程    
    {    
        Worker();    
        exit(10);    
    }    
    else    
    {//父进程    
        printf("wait before!\n");//回收之前    
        int status;    
        pid_t rid = waitpid(id, &status,0);    
        printf("wait after!\n");//回收之后    
        if(rid == id)    
        {//我们不能对status整体使用    
            printf("wait a process,pid: %d, rpid: %d, exit sig: %d, exit code: %d\n",getpid(),rid, status&0x7F, (status>>8)&0xFF);    
        }    
        sleep(5);    
    }    
    return 0;    
}   

(四)图示说明父进程等待子进程的方式

(五)阻塞等待和非阻塞等待

  • 阻塞式等待:当父进程调用 wait 或者 waitpid(第三个参数为0)等待子进程时,如果子进程还未退出,父进程会被阻塞,暂停执行,直到子进程退出并返回其状态信息。

  • 非阻塞式等待:当父进程调用 waitpid(第三个参数为 WNOHANG)等待子进程时,如果父进程检测到子进程未退出,父进程并不会在原地等待,而是继续执行自己的代码。父进程可以通过检查返回值来判断子进程的状态,如果返回0表示子进程还在运行,非0表示子进程已退出。

 非阻塞式等待代码举例:

#include<stdio.h>
#include<stdlib.h>//exit的头文件
#include<unistd.h>//fork头文件
#include<sys/types.h>//pid_t类型的头文件
#include<sys/wait.h>//wait头文件
   
    
#define TASK_NUM 5    
typedef void (*task_t)();//函数指针    
    
void InitTasks(task_t tasks[], int num)//初始化任务    
{    
    for(int i =0;i<num;i++)    
    {    
        tasks[i] = NULL;    
    }    
}    
int  AddTask(task_t tasks[], task_t t)//添加任务    
{    
    int i= 0;    
    for(;i<TASK_NUM;i++)                                                                                             
    {    
        if(tasks[i] == NULL)    
        {    
            tasks[i] = t;    
            return 1;    
        }    
    }    
    return 0;    
}    
void executeTask(task_t tasks[], int num)//执行任务                                                                  
{    
    for(int i =0;i<num;i++)    
    {    
        if(tasks[i])    
        {    
            tasks[i]();    
        }
    }
}
void Worker(int cnt)//子进程要做的工作
{
    printf("I am a child, pid: %d, cnt: %d\n",getpid(),cnt);
}
void download()
{
    printf("this is a download task is running\n");
}

void printlog()
{
    printf("this is a write log task is running\n");
}

void show()
{
    printf("this is a show info tsak is running\n");
}
int main()
{
    task_t tasks[TASK_NUM];
    InitTasks(tasks, TASK_NUM);//初始化任务
    AddTask(tasks, download);
    AddTask(tasks, printlog);
    AddTask(tasks, show);
    pid_t id = fork();
    if(id == 0)//子进程
    {
        int cnt = 5;
        while(cnt)
        {
            Worker(cnt);
            sleep(2);                                                                                                                                        
            cnt--;
        }
        exit(0);
    }
    else
    {//父进程
        while(1)//父进程对子进程的状态进程轮询
        {
            int status=0;
            pid_t rid = waitpid(id,&status,WNOHANG);//由于第3个参数是WNOHANG,所以子进程如果没有结束将直接返回0
            if(rid>0)
            {
                //子进程退出,返回子进程的pid
                printf("child quit success, exit code: %d, exit signal: %d\n",(status>>8)&0xFF,status&0x7F);
                break;
            }
            else if (rid == 0)
            {
                printf("##################################\n");
                //等待的进程暂时没有退出
                printf("child is alive, wait again, father do other thing....\n");
                executeTask(tasks, TASK_NUM);//执行任务
            }
            else{
                //等待失败,waitpid中的第一个参数传错了
                printf("wait failed!\n");
                break;
            }
            sleep(1);
        }
    }
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值