【Linux】实现一个简易的进程池
🦄个人主页:修修修也
🎏所属专栏:Linux
⚙️操作环境:VS Code (操作系统:Ubuntu 22.04 server 64bit)
目录
池化技术
进程池的设计原理
进程池实现整体思路
进程池实现完整代码
Test.hpp文件
ProcessPool.cc文件
makefile文件
结语
池化技术
在之前学习STL的时候我们曾经了解过内存池的概念,今天我们要实现的进程池和内存池一样,都是一种池化技术:
池化技术 (Pool) 是一种很常见的编程技巧,在请求量大时能明显优化应用性能,降低系统频繁建连的资源开销。我们日常工作中常见的有数据库连接池、线程池、对象池等,它们的特点都是将 “昂贵的”、“费时的” 的资源维护在一个特定的 “池子” 中,规定其最小连接数、最大连接数、阻塞队列等配置,方便进行统一管理和复用,通常还会附带一些探活机制、强制回收、监控一类的配套功能。
进程池的设计原理
我们一次性创建10个子进程,然后通过一个vector将它们组织起来管理,再建立10个相应的管道和它们通信,为这10个进程发布任务,其底层关系如下图所示(虚线表示创建过联系但不需要所以关闭了):
注意,这个图仅作示意,为了帮助大家先初步搞懂父进程和子进程还有管道文件之间的关系,实际后续实现时我们可能会有别的设计,或许实现后的真实关系并不像下图这样,请大家注意不要混淆.
进程池实现整体思路
进程池整体思路如下图:
进程池实现完整代码
Test.hpp文件
#pragma once #include<iostream> #include<vector> //定义一个函数指针 typedef void (*task_t)(); void task1() { std::cout<< "刷新日志"<<std::endl; } void task2() { std::cout<< "刷新野怪"<<std::endl; } void task3() { std::cout<< "检测软件是否更新"<<std::endl; } void task4() { std::cout<< "用户释放技能,更新技能cd及蓝量"<<std::endl; } void LoadTask(std::vector<task_t> *tasks) { tasks->push_back(task1); tasks->push_back(task2); tasks->push_back(task3); tasks->push_back(task4); }
ProcessPool.cc文件
#include"Task.hpp" #include<string> #include<vector> #include<cstdlib> #include<cassert> #include<unistd.h> #include<time.h> #include<sys/wait.h> #include<sys/stat.h> const int processnum = 10; std::vector<task_t> tasks; //先描述 class channel { public: channel(int cmdfd, pid_t slaverid, std::string &processname) :_cmdfd(cmdfd) ,_slaverid(slaverid) ,_processname(processname) {} public: int _cmdfd; //发送任务的文件描述符 pid_t _slaverid; //子进程的PID std::string _processname; //子进程的名字 -- 方便我们打印日志 }; void slaver() { while(true) { int cmdcode = 0; int n = read(0, &cmdcode, sizeof(int));; if(n == sizeof(int)) { //执行cmdcode对应任务列表 std::cout <<"slaver say@ get a command " << getpid() << " cmdcode:" << cmdcode << std::endl; if(cmdcode >= 0 && cmdcode < tasks.size()) tasks[cmdcode](); } if(n == 0) break; } } //编码规范 //输入 const & //输出 * //输入输出 & void InitProcessPool(std::vector<channel> *channels) { //确保子进程只有一个写端 //使用数组是因为父进程的写端一直存在,开了多少子进程就会有多少写端 //子进程越靠后,从父进程继承的越多 std::vector<int> oldfds; for(int i=0; i<processnum; i++) { int pipefd[2]; //临时空间 int n = pipe(pipefd); //创建管道 assert(!n); (void)n; pid_t id = fork(); if(id == 0) //child { for(auto fd : oldfds) close(fd);//关闭子进程继承的父进程的写端 close(pipefd[1]); dup2(pipefd[0],0); //替换子进程的标准输入端和管道输入端,这样后续子进程需要读数据直接从0号文件读就行 close(pipefd[0]); slaver(); std::cout<<"process : "<<getpid()<<" quit"<<std::endl; exit(0); } //father close(pipefd[0]); //添加channel字段 std::string name = "process-" + std::to_string(i); channels->push_back(channel(pipefd[1], id, name)); oldfds.push_back(pipefd[1]); } } void Debug(const std::vector<channel> &channels) { //test for(const auto &c : channels) { std::cout<<c._cmdfd<<" "<<c._slaverid<<" "<<c._processname<<std::endl; } } void Menu() { std::cout<<"******************************"<<std::endl; std::cout<<"* 1.刷新日志 *"<<std::endl; std::cout<<"* 2.刷新野怪 *"<<std::endl; std::cout<<"* 3.检测软件更新 *"<<std::endl; std::cout<<"* 4.更新cd及蓝量 *"<<std::endl; std::cout<<"* 0.退出 *"<<std::endl; std::cout<<"******************************"<<std::endl; } void ctrlSlaver(const std::vector<channel> &channels) { int which = 0; //for(int i = 1;i<=10;i++) //int cnt = 5; while(true) { //用户手动控制 int select = 0; Menu(); std::cout<<"please Enter@ "; std::cin>>select; if(select <= 0 || select >= 5) break; //1.选择任务 //int cmdcode = rand()%tasks.size(); int cmdcode = select - 1; //2.选择进程 :要负载均衡1.随机数2.轮询 //随机选择 //int processpos = rand()%channels.size(); std::cout<<"father say: "<<"cmdcode: "<<cmdcode <<" already sendto "<<channels[which]._slaverid <<" process name: "<<channels[which]._processname <<std::endl; //3.发送任务 :向管道里发送数据(任务编码) write(channels[which]._cmdfd,&cmdcode,sizeof(cmdcode)); //轮询选择 which++; which%=channels.size(); //cnt--; sleep(1); } } void QuitProcess(const std::vector<channel> &channels) { for(const auto &c : channels) { close(c._cmdfd); waitpid(c._slaverid,nullptr,0); } //for(const auto &c : channels) close(c._cmdfd); //sleep(5); //for(const auto &c : channels) waitpid(c._slaverid,nullptr,0); //sleep(5); //因为有子进程会继承父进程对其他进程的管道的写端的问题, //所以需要倒着回收进程,这样从后向前依次消解多人指向管道文件导致文件无法关闭的情况 // int last = channels.size()-1; // for(int i=last; i>=0;i--) // { // close(channels[i]._cmdfd); // waitpid(channels[i]._slaverid,nullptr,0); // } } int main() { LoadTask(&tasks); srand(time(nullptr)^getpid()^1023); //种随机数种子 //再组织 std::vector<channel> channels; //1.初始化 InitProcessPool(&channels); Debug(channels); //2.开始控制子进程 ctrlSlaver(channels); //3.清理收尾 :关闭创建的所有进程 QuitProcess(channels); return 0; }
makefile文件
ProcessPool:ProcessPool.cc g++ -o $@ $^ -g -std=c++11 .PHONY:clean clean: rm -f ProcessPool
结语
希望这篇关于 实现一个简易的进程池 的博客能对大家有所帮助,欢迎大佬们留言或私信与我交流.
学海漫浩浩,我亦苦作舟!关注我,大家一起学习,一起进步!
相关文章推荐
【Linux】进程通信之管道
【Linux】进程间通信
【Linux】实现一个简易的shell命令行
【Linux】操作系统与进程
【Linux】实现三个迷你小程序(倒计时,旋转指针,进度条)
【Linux】手把手教你从零上手gcc/g++编译器
【Linux】手把手教你从零上手Vim编辑器
【Linux】一文带你彻底搞懂权限
【Linux】基本指令(下)
【Linux】基本指令(中)
【Linux】基本指令(上)