Linux_进程池

目录

1、进程池基本逻辑

2、实现进程池框架

3、文件描述符的继承

4、分配任务给进程池 

5、让进程池执行任务 

6、回收子进程 

7、进程池总结 

结语 


前言:

        在Linux下,进程池表示把多个子进程用数据结构的方式进行统一管理,在任何时候都可以对进程池里的子进程进行任务发放,即进程池可以实现并发执行流,能够同时执行多个任务,相比于单进程单一执行流,进程池在处理多任务的效率上有了显著提升。

1、进程池基本逻辑

        进程池之所以能够实现多任务的并发执行,是因为进程池本质是进程间通信(即主进程以通信的形式向进程池里的进程发送任务),并且进程池大部分是由父子进程实现的,所以可以使用匿名管道来实现进程池,说到匿名管道就离不开接口pipe,该接口介绍如下:

#include <unistd.h>//pipe所需要的头文件//传一个数组给到pipe,pipe调用成功时返回0,失败返回-1
//调用成功pipefd数组的第一个元素是读的下标,第二个元素是写的下标
int pipe(int pipefd[2]);

         有了此接口就能够搭配fork接口实现父进程与多个子进程的通信了,上面提到进程池是由数据结构对子进程进行管理的,因此我们需要一个数据结构来方便控制子进程,并且可以让父进程通过该数据结构来调度子进程,所以可以定义一个类来描述子进程,代码如下:

class process
{
public:process(int id, const string &name, int fd): id_(id), name_(name), fd_(fd){}~process(){}public:pid_t id_;    // 子进程idstring name_; // 子进程名称int fd_;      // 控制子进程的管道写端(真正控制子进程就是写端的文件描述符)
};

        可以理解为创建一个子进程就用process类来描述他,因此创建10个子进程就会有10个process实例化的对象,然后对这10个进行管理即对10个子进程进行管理,可以把这10个对象放到vector内,vector就是管理进程池的数据结构。

        至此有了上面的概念,就可以搭建基本的进程池框架了,但是要注意一点,即父进程只读,子进程只写,因此要关闭对应的文件描述符,因为管道是半双工通信,只能一边写一边读,示意图如下:

        进程池思路:利用for循环10次,循环调用pipe接口和fork接口,这样就能创建十个子进程,并且每个子进程都有一个匿名管道来与父进程进行通信。当父进程关闭了3号文件描述符后,下一次父进程再次调用pipe接口时,依旧是3号为读端,这样一来创建的子进程统一读端都是3号(因为子进程继承父进程的文件描述符,所以子进程继承了3号读端描述符),子进程的一致性就有了。

2、实现进程池框架

        用代码实现进程并简单测试,代码如下:

#include <iostream>
#include <unistd.h>
#include <vector>
#include <error.h>
#include <string>
#include <sys/types.h>using namespace std;
#define N 10class process//描述单个进程的类
{
public:process(int id, const string &name, int fd): id_(id), name_(name), fd_(fd){}~process(){}public:pid_t id_;    // 子进程idstring name_; // 子进程名称int fd_;      // 控制子进程的管道写端(真正控制子进程就是写端的文件描述符)
};int main()
{vector<process> vpr;//管道子进程的数据结构vector<int> oldfd;//记录写端的文件描述符for (int i = 0; i < N; i++)//创建十个子进程的进程池{int pipefd[2];int n = pipe(pipefd);//父进程创建管道if (n == -1){perror("pipe");exit(-1);}// 创建子进程pid_t id = fork();//子进程执行流if (id == 0){for(auto num:oldfd) close(num);//关闭新子进程继承父进程之前的写端close(pipefd[1]);//因为子进程只负责读数据,所以关闭子进程对管道的写端dup2(pipefd[0], 0); // 重定向,这一步只是方便后续的测试exit(0);}// 父进程执行流close(pipefd[0]);//父进程只写,因此关闭父进程对管道的读端string pro_name = "子进程" + to_string(i);vpr.push_back({id, pro_name, pipefd[1]});//把子进程的信息插入到数据结构中oldfd.push_back(pipefd[1]);//记录父进程新打开的写端}for (auto &num : vpr)//验证进程池里的进程{cout << num.name_ << ":" << num.id_ << ":" << num.fd_ << endl;}return 0;
}

        运行结果:

        从测试结果可以发现,确实生成了十个子进程,并且可以通过vector找到他们,但是上述代码中多创建了一个vector<int> oldfd,这个vector是做什么的呢? 

3、文件描述符的继承

        我们都知道一个子进程是会继承父进程的PCB结构体的,自然也会继承PCB结构体里的所有内容,而文件描述符就是PCB结构体里的内容之一,所以文件描述符理应被子进程继承,那么在进程池中就会面临这样一个问题:虽然子进程关闭了写端,但是父进程的写端是会越来越多的,而每次创建的子进程只关闭新的写端,会导致新创建的子进程继承了父进程之前打开的写端,并且这些写端没有得到关闭,具体示意图如下:

        所以随着越来越多的管道被创建,后续创建的子进程会有大量的写端被打开,并且他们都是指向前面子进程的管道,因此需要用vector记录每一次父进程新打开的写端,因为这些新打开的写端也会拷贝到子进程中,所以在子进程中遍历该vector就能关闭子进程继承而来的写端,这就是vector<int> oldfd的作用。

4、分配任务给进程池 

        有了上述的进程池框架,接下来就可以对进程池中的每个进程分配任务了,再次之前可以先定义一个任务列表,用函数指针的方式将这些任务用vector管理起来,表示进程池即将处理的任务,任务列表如下: 

typedef void (*task)();
vector<task> vt; // 任务队列void task1()
{cout << "检测当前角色健康状态" << endl;
}void task2()
{cout << "检测当前角色物品补给" << endl;
}void task3()
{cout << "检测当前角色生命值" << endl;
}void task4()
{cout << "检测当前角色法力值" << endl;
}void creator_task(vector<task> &vt)//把任务插入到任务队列中
{vt.push_back(task1);vt.push_back(task2);vt.push_back(task3);vt.push_back(task4);
}

         有了任务列表后父进程就可以分配任务了,因为任务列表本身是一个vector,并且里面存放的是函数指针,因此父进程给子进程传递vector的下标,这个过程就是任务的派发,子进程拿到下标就可以拿到vector的元素并且调用,这个过程就是任务的执行,父进程派发任务的代码如下:

//父进程开始派送任务cout<<"主进程开始给子进程分配任务"<<endl;sleep(2);for (int i = 0; i < 5; i++){int proc_num = rand()%N;//随机子进程-vpr下标int task_num = rand()%4;//随机任务write(vpr[proc_num].fd_,&task_num,sizeof(int));//分配任务的核心就是进程间通信sleep(1);}

        从上述代码中可以发现,让进程池执行任务的本质就是父进程通过调用write函数传递任务列表的下标给到子进程,这就是为什么进程池是通过进程间通信实现的

5、让进程池执行任务 

        执行任务主要是子进程的工作,所以在子进程的执行流中还要添加一个等待任务的动作,因为进程池的本质是进程间通信,所以子进程等待任务的动作就是调用read函数,等父进程往匿名管道中写数据(等待任务就是read函数的阻塞),子进程拿到这些数据就可以执行对应的任务了,下面是子进程等待任务的代码:

void chlid_go()//让子进程执行任务
{int task = 0;while (true){int n = read(0, &task, sizeof(int));//读取的内容就是任务列表的下标if (n > 0){cout << "处理该任务的进程是:" << getpid() << ":";vt[task]();//根据读取到的下标去调用任务列表里的函数指针}elsebreak;}
}

        把该函数填写到子进程的执行流中,就能让子进程执行任务了,代码如下:

#include <iostream>
#include <unistd.h>
#include <vector>
#include <error.h>
#include <string>
#include <sys/types.h>
#include <time.h>using namespace std;
#define N 10typedef void (*task)();
vector<task> vt; // 任务队列//自定义任务列表
void task1()
{cout << "检测当前角色健康状态" << endl;
}void task2()
{cout << "检测当前角色物品补给" << endl;
}void task3()
{cout << "检测当前角色生命值" << endl;
}void task4()
{cout << "检测当前角色法力值" << endl;
}void creator_task(vector<task> &vt)
{vt.push_back(task1);vt.push_back(task2);vt.push_back(task3);vt.push_back(task4);
}//子进程执行任务
void chlid_go()
{int task = 0;while (true){int n = read(0, &task, sizeof(int));//读取的内容就是任务列表的下标if (n > 0){cout << "处理该任务的进程是:" << getpid() << ":";vt[task]();//根据读取到的下标去调用任务列表里的函数指针}elsebreak;}
}class process//描述单个进程的类
{
public:process(int id, const string &name, int fd): id_(id), name_(name), fd_(fd){}~process(){}public:pid_t id_;    // 子进程idstring name_; // 子进程名称int fd_;      // 控制子进程的管道写端(真正控制子进程就是写端的文件描述符)
};int main()
{creator_task(vt);srand(time(0));vector<process> vpr;//管道子进程的数据结构vector<int> oldfd;//记录写端的文件描述符for (int i = 0; i < N; i++)//创建十个子进程的进程池{int pipefd[2];int n = pipe(pipefd);//父进程创建管道if (n == -1){perror("pipe");exit(-1);}// 创建子进程pid_t id = fork();//子进程执行流if (id == 0){for(auto num:oldfd) close(num);//关闭新子进程继承父进程之前的写端close(pipefd[1]);//因为子进程只负责读数据,所以关闭子进程对管道的写端dup2(pipefd[0], 0); // 重定向,这一步只是方便后续的测试chlid_go();exit(0);}// 父进程执行流close(pipefd[0]);//父进程只写,因此关闭父进程对管道的读端string pro_name = "子进程" + to_string(i);vpr.push_back({id, pro_name, pipefd[1]});//把子进程的信息插入到数据结构中oldfd.push_back(pipefd[1]);//记录父进程新打开的写端}for (auto &num : vpr)//验证进程池里的进程{cout << num.name_ << ":" << num.id_ << ":" << num.fd_ << endl;}//父进程开始派送任务cout<<"主进程开始给子进程分配任务"<<endl;sleep(2);for (int i = 0; i < 5; i++){int proc_num = rand()%N;//随机子进程-vpr下标int task_num = rand()%4;//随机任务write(vpr[proc_num].fd_,&task_num,sizeof(int));//分配任务的核心就是进程间通信sleep(1);}return 0;
}

        运行结果:

        至此就完成了给进程池里的进程随机派发任务的实现。 

6、回收子进程 

        上述代码结束后没有对子进程做任何的等待工作,但是结果也是正确的,原因就是当父进程退出后,会关闭父进程所有对匿名管道的写端,写端一关闭,则匿名管道的读端就会读到文件末尾,因此read会返回0,在上面代码中当read返回0时就会跳出循环,从而继续往下执行直到exit退出当前子进程,所以父进程不等待子进程则也不会导致孤儿进程问题(因为父进程退出则子进程一定也会退出)。

        但是为了保证代码、逻辑的完整性,最好还是写一个专门关闭写端和等待子进程的函数加到上述代码的末尾处,代码如下:

for (int i = 0; i < vpr.size(); i++){close(vpr[i].fd_);//关闭父进程写端waitpid(vpr[i].id_, nullptr, 0);//等待关闭写端的对应子进程cout<<"等待子进程:"<<vpr[i].name_<<endl;sleep(1);}

        测试结果:

7、进程池总结 

        1、进程池通过匿名管道进行父子进程通信而实现的。

        2、进程池控制子进程的策略是通过父进程对匿名管道的写端文件描述符,一个写端对应一个子进程。

        3、注意文件描述符继承的问题,从逻辑上来说子进程要关闭继承父进程的写端文件描述符,即一个子进程只留下对应匿名管道的读端,而父进程要关闭自己的读端。 

        4、默认无其他文件描述符,则子进程的读端始终是3号(因为父进程每次都会关闭3号,导致下一次pipe还是3号为读端),并且所有子进程的读端文件描述符是一样的,父进程的写端从4号开始按顺序往下排。

结语 

        以上就是关于进程池的实现与讲解,进程池允许并发式的执行任务,因此常用进程池处理多任务的场景,并且进程池传递任务和处理任务时就是通过匿名管道传递信息,然后子进程对该信息做解释以达到处理任务的效果。

        最后如果本文有遗漏或者有误的地方欢迎大家在评论区补充,谢谢大家!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/40896.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

18. JAVA 多线程锁介绍

1. 前言 本节内容主要是对 Java 多线程锁进行介绍&#xff0c;是对锁的一个全方位的概述&#xff0c;为我们对后续深入学习不同的锁的使用方法奠定一个良好的基础。本节内容的知识点如下&#xff1a; 乐观锁与悲观锁的概念&#xff0c;以及两种锁之间的区别&#xff0c;这是并…

【unity实战】使用unity的新输入系统InputSystem+有限状态机设计一个玩家状态机控制——实现玩家的待机 移动 闪避 连击 受击 死亡状态切换

最终效果 文章目录 最终效果前言人物素材新输入系统InputSystem的配置动画配置代码文件路径状态机脚本创建玩家不同的状态脚本玩家控制源码完结 前言 前面我们已经写过了使用有限状态机制作一个敌人AI&#xff1a;【unity实战】在Unity中使用有限状态机制作一个敌人AI 那么玩…

2024华为OD机试真题-找数字-(C++/Python)-C卷D卷-200分

2024华为OD机试题库-(C卷+D卷)-(JAVA、Python、C++) 题目描述 小扇和小船今天又玩起来了数字游戏, 小船给小扇一个正整数 n(1 ≤ n ≤ 1e9),小扇需要找到一个比 n 大的数字 m,使得 m 和 n 对应的二进制中 1 的个数要相同,如: 4对应二进制1008对应二进制1000其中1的个数…

【苍穹外卖】Day1遇到的问题

1、lombok版本不兼容问题 java: java.lang.IllegalAccessError: class lombok.javac.apt.LombokProcessor (in unnamed module 0x3278991b) cannot access class com.sun.tools.javac.processing.JavacProcessingEnvironment (in module jdk.compiler) because module jdk.comp…

Java项目:基于SSM框架实现的毕业论文管理系统【ssm+B/S架构+源码+数据库+毕业论文】

一、项目简介 本项目是一套基于SSM框架实现的毕业论文管理系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操作简单、功能…

javaScript(九) 数组

] console.log(af.pop()) console.log(af) 第一个输出&#xff1a;{id:2,name:“枷”,score:“98”} 第二个输出&#xff1a;[ {id:1,name:“My”,score:“90”}, {id:3,name:“123”,score:“80”} ] Array.prototype.shift() 删除数组中的第一个元素&#xff0c;该方法…

一个项目学习Vue3---Vue计算属性

观察下面一段代码&#xff0c;学习Vue计算属性 <template><div><span>用户大于10岁的数量&#xff1a;{{ userVue.filter(user>user.age>10).length}}</span><span>用户大于10岁的数量2&#xff1a;{{ userAgeltTen}}</span><sp…

基于轨迹信息的图像近距离可行驶区域方案验证

一 图像可行驶区域方案 1.1 标定场景 1.2 标定步骤 设计一定间距标定场&#xff0c;在标定场固定位置设置摄像头标定标识点。主车开到标定场固定位置录制主车在该位置各个摄像头数据&#xff0c;通过摄像头捕获图像获取图像上关键点坐标pts-2d基于标定场设计&#xff0c;计算…

保函到期提醒是银行或金融机构提供的一项服务,旨在确保客户及时了解保函即将到期的情况,从而避免因保函过期而导致的风险或违约责任。

保函到期提醒是银行或金融机构提供的一项服务&#xff0c;旨在确保客户及时了解保函即将到期的情况&#xff0c;从而避免因保函过期而导致的风险或违约责任。以下是保函到期提醒的一些关键方面&#xff1a; 1. **保函定义**&#xff1a; - 保函是一种由银行出具的书面承诺&…

vue实现左右拖动分屏

效果图如下&#xff1a; 封装组件 <template><div ref"container" class"container"><div class"left-content" :style"leftStyle">/**定义左侧插槽**/<slot name"left"></slot></div>…

Springboot+Vue3开发学习笔记《2》

SpringbootVue3开发学习笔记《2》 博主正在学习SpringbootVue3开发&#xff0c;希望记录自己学习过程同时与广大网友共同学习讨论。 总共涉及两部分&#xff0c;第一部分为基础部分学习&#xff0c;第二部分为实战部分。 一、学习路径 1.1 基础部分 配置文件整合MyBatisBea…

中英双语介绍美国的州:阿拉斯加州(Alaska)

中文版 阿拉斯加州&#xff08;Alaska&#xff09;位于美国西北角&#xff0c;是美国面积最大的州&#xff0c;以其壮丽的自然景观、丰富的矿产资源和独特的野生动物闻名。以下是对阿拉斯加州的详细介绍&#xff0c;包括其地理位置、人口、经济、教育、文化和主要城市。 地理…

QQ聊天记录删除了怎么恢复?这4个方法让你秒找回!

在现代社会&#xff0c;QQ已经成为我们日常交流和工作中不可或缺的沟通工具。然而&#xff0c;有时我们可能会不小心删除了重要的聊天记录&#xff0c;这会带来诸多不便甚至困扰。那么&#xff0c;当你发现自己误删了数据&#xff0c;qq聊天记录删除了怎么恢复呢&#xff1f;有…

第14届蓝桥杯Python青少组中/高级组选拔赛(STEMA)2022年8月21日真题

第14届蓝桥杯Python青少组中/高级组选拔赛&#xff08;STEMA&#xff09;2022年8月21日真题 题目总数&#xff1a;5 总分数&#xff1a;128 更多真题下载点我&#x1f446; 编程题 第 1 题 问答题 编程实现&#xff1a; 给定一个正整数&#xff0c;输出正整数个位上的…

tsconfig.json的include和exclude作用

tsconfig.json中的include和exclude属性用于指定需要被编译的TypeScript文件和需要被排除的文件。‌ include属性&#xff1a;‌用于指定哪些.ts、‌.tsx或.d.ts文件需要被编译。‌如果不指定include属性&#xff0c;‌则默认当前目录下除了exclude之外的所有.ts、‌.d.ts、‌…

昇思25天学习打卡营第11天|LSTM+CRF序列标注

序列标注指给定输入序列&#xff0c;给序列中每个Token进行标注标签的过程。序列标注问题通常用于从文本中进行信息抽取&#xff0c;包括分词(Word Segmentation)、词性标注(Position Tagging)、命名实体识别(Named Entity Recognition, NER)等。 和人理解语言一样&#xff0c…

2024-07-04 base SAS programming学习笔记8(HTML)

当使用ODS来进行结果或数据集输出的时候&#xff0c;可以同时设置多个ODS 命令&#xff0c;同时输出到多个不同的文件。使用_ALL_ 表示关闭所有的ODS输出窗口&#xff0c;比如&#xff1a; ods html file(body)"html-file-pathname"; ods html file"pdf-file-pa…

【C#】如何在窗体程序中调用多行CMD命令

【背景】 用VS写一个C#窗体程序&#xff0c;第一步需要用CMD启动一个外部服务并发送信息给该服务器&#xff0c;涉及两步命令&#xff0c;第一步是启动服务&#xff0c;第二步是发送信息。 【分析】 要点&#xff1a; 如何指定启动CMD的路径在服务exe所在路径下&#xff1b…

中国东方资产管理25届秋招北森测评笔试如何高分通过?真题考点分析看完这篇就够了

一、东方资管校招测评题型分析 中国东方资产管理股份有限公司&#xff08;中国东方资管&#xff09;的校园招聘测评题型主要包括以下几个部分&#xff1a; 1. **计分题&#xff0c;行测知识**&#xff1a;这部分题量大约在56-57题左右&#xff0c;分为不同的模块进行计时测试。…