非阻塞式等待和进程程序替换
一.非阻塞式等待
1.什么是非阻塞式进程等待?
我们都知道,进程等待wiat_pid有两种,一个是阻塞式,一个是非阻塞式;
#include <sys/wait.h>
#include <sys/types.h>pid_t waitpid(pid_t pid, int *status, int options);
将options设置为0,这就是阻塞等待;
将options设置为 WNOHANG
,就是非阻塞等待;
2.非阻塞等待的好出是什么?
既然父进程不需要阻塞,那就说明可以干其他的事情,所以,相比较阻塞式,非阻塞的进程效率更高;
3.代码案例:
简单的创建一个子进程,让子进程等待3秒后退出,一直让父进程等待访问,若等到进程退出,就等待成功,若子进程还没未退出,就显示进程还未退出,若进程等待失败就显示失败:
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<string.h>int main(){int i = 0;pid_t id = fork();if(id == 0){int i = 3;while(1){printf("我是一个子进程, pid:%d ppid:%d ,cout:%d\n",getpid(),getppid(),i);sleep(1);i--;}//退出printf("子进程退出\n");exit(0);}printf("父进程开始等待子进程...\n");//父进程int status = 0;//非阻塞的等待while(1){pid_t rid = waitpid(id,&status,WNOHANG);if(rid == id){//等待成功了printf("wait success 子进程Id: %d\n",rid);break;} else if(rid == 0){//还在等待中printf("子进程还未退出...\n");sleep(1);} else if (rid <0){printf("等待失败了\n");break;}}sleep(2);return 0;
}
运行结果:
显然结果是符合预期的,进程退出后,父进程将其等待成功,什么时候会等待失败呢?基本就是waitpid中的id值无效的时候,就会等待失败。
代码案例,当我等待一个不存在的id:
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<string.h>int main(){int i = 0;pid_t id = fork();if(id == 0){int i = 3;while(i){printf("我是一个子进程,pid:%d ppid:%d ,cout:%d\n",getpid(),getppid(),i);sleep(1);i--;}//退出printf("子进程退出\n");exit(1);}printf("父进程开始等待子进程...\n");//父进程int status = 0;//非阻塞的等待while(1){//等待一个不存在的数字pid_t rid = waitpid(id+1,&status,WNOHANG);if(rid == id){//等待成功了2295209printf("wait success 子进程Id:%d\n",rid);break;}else if(rid == 0){//还在等待中printf("子进程还未退出...\n");sleep(1);}else if (rid <0){printf("等待失败了,rid:%d\n",rid);break;}}sleep(2);return 0;
}
结果是等待失败并且还崩溃了:
既然说非阻塞等待可以更高的效率,那么,我们就通过一个例子来表现他的高效:
首先我写里一个tools.hpp文件和一个Task.hpp文件:
#pragma once
#include<iostream>
#include<vector>
#include<functional>using func_t = std::function<void()>;
//typedef std::function<void()> func_t
class Tool{public:Tool(){};~Tool(){};void PushFunc(func_t f){_funcs.push_back(f);}void Execute(){for(auto& f:_funcs){f();}}private:std::vector<func_t> _funcs;
};
#pragma once
#include<iostream>
void DownLoad(){std::cout<<"这是一个下载任务"<<std::endl;
}
void PrintLog(){std::cout<<"这是一个打印任务"<<std::endl;
}
void FlushData(){std::cout<<"这个一个刷新数据任务"<<std::endl;
}
再将上面的两个hpp文件包含在wait.cc文件中,(什么是hpp文件呢?简单的说,可以看成一个头文件,不过,这个头文件中可以写方法的具体实现,被.c文件include后,可以直接使用,这样的好处就是避免繁琐的头文件和代码分离的操作,缺点就是有点乱)然后在父进程等待的时候对他进行执行:
代码:
#include<sys/wait.h>
#include<unistd.h>
#include<string.h>
#include"tools.hpp"
#include"Task.hpp"int main(){Tool tool;tool.PushFunc(DownLoad);tool.PushFunc(PrintLog);tool.PushFunc(FlushData);int i = 0;pid_t id = fork();if(id == 0){int i = 3;while(i){printf("我是一个子进程,pid:%d ppid:%d ,cout:%d\n",getpid(),getppid(),i);sleep(1);i--;}//退出printf("子进程退出\n");exit(1);}printf("父进程开始等待子进程...\n");//父进程int status = 0;//非阻塞的等待while(1){//等待一个不存在的数字pid_t rid = waitpid(id,&status,WNOHANG);if(rid == id){printf("wait success 子进程Id:%d\n",rid);break;}else if(rid == 0){//还在等待中printf("子进程还未退出...\n");tool.Execute();sleep(1);}else if (rid <0){printf("等待失败了,rid:%d\n",rid);break;}}return 0;
}
执行结果:符合预期的,当父进程等待时,可以进行其他的操作
二.进程程序替换
上面的代码中的子进程我们似乎都是执行的是整个父进程中的一部分代码,那么我们能不能执行子进程自己的代码呢?或者执行本地磁盘中其他程序的代码呢?
我们先来认识一下这个函数:
int execclp(const char *file, const char *arg, ..., (char *)NULL);
上面的这个函数是进程替换相关函数的一个,它能让子进程执行跳出该代码程序外的其他的程序,第一个参数file 是要执行的指令;第二个可变参数是如何执行这条指令,可长可短;第三个必须是显示的填NULL;
案例代码:子进程从父进程那获得命令行参数列表,然后执行这个指令
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>int main(int argc, char* argv[]){pid_t id = fork();if(id == 0){sleep(2);printf("开始执行子进程\n");// 修正:execclp 的参数需要逐个传递,且最后以 NULL 结尾execclp(argv[1], argv[1], NULL);printf("子进程执行结束\n");exit(1);}printf("父进程开始等待...\n");sleep(1);pid_t rid = waitpid(id, NULL, 0);if(rid == id){printf("等待成功\n");}return 0;
}
结果如下:
根据上面的例子可以看出,我们可思考一个问题:我们在Linux中输入的普通的指令,是否也是通过这样的形式,被一个系统中的父进程传给一个子进程,然后让子进程执行,父进程得到子进程的退出码,所以我们才会通过输入 echo $? 打印出上一条指令(程序)的退出码的?既然如此,我们似乎是否也能写出一个shell呢?里面的指令也由自己编写,然后执行这些指令?