实现一个进程池(精讲)

目录

写进程池前的理论扫盲

进程池的实现


写进程池前的理论扫盲

父进程创建子进程,父子俩都看见同一片资源,这片资源被俩进程利用,用来通信,这片资源就是管道,如图所示,能很好地诠释管道。

那么什么是进程池呢?

我画了一副图,这幅图也很好的解释了什么是进程池,父进程通过管道给子进程分配任务,或者通信,有多个进程,就叫作进程池

而我们开始之前,已经将编译器转换成了vscode,环境变成了Ubuntu,语言使用C++。按照上图来,父进程向管道写,子进程从管道读。

进程池的实现

进程池最重要的就是管道,首先就要在头文件里面写类,首先就是管道类,然后是管道组织类,因为我们要把管道组织起来,最后就是进程池类,我们需要对进程池实现一些函数,首先,有一个头文件 .hpp和 .cc文件.

#include<iostream>
#include<vector>//管道类,先描述
class channel
{
public:channel(){}~channel(){}private:};const int gdefaultnum = 5;//管道数量,暂时定为5个//管道管理类,再组织
class ChannelManage
{
public:ChannelManage(){}~ChannelManage(){}
private:std::vector<channel> _Channels;
};//进程池类
class ProcessPool
{
public:ProcessPool(){}~ProcessPool(){}
private:ChannelManage _cm;
};

管道管理类的变量就是一个内容为 channel 的 vector ,名字叫 _Channels,而进程池类的变量就是这个vector。我们还定义了一个全局变量,gdefaultnum,表示为管道数量。

接下来,我们该创建管道了。将函数定为start,我们看看怎么写的。

#ifndef _PROCESS_POOL_HPP_
#define _PROCESS_POOL_HPP_#include<iostream>
#include<vector>
#include<unistd.h>
#include<cstdlib>//管道类,先描述,建立信道
class channel
{
public:channel(int fd,pid_t pid):_wfd(fd),pid(pid){std::cout << "channel-" <<std::to_string(_wfd)+std::to_string(pid);}~channel(){}private:int _wfd;pid_t pid;std::string name;};const int gdefaultnum = 5;//管道数量,暂时定为5个//管道管理类,再组织
class ChannelManage
{
public:ChannelManage(){}void BuildChannel(int wfd,pid_t pid ){//vector的一个函数,不需要构建临时对象,直接就可以尾插到vector里面_Channels.emplace_back(wfd,pid);}~ChannelManage(){}
private:std::vector<channel> _Channels;
};//进程池类
class ProcessPool
{
public:ProcessPool():_process_num(gdefaultnum){}void Work(int rfd){std::cout<<"子进程工作" <<std::endl;}bool Start(){for (int i = 0; i < _process_num; i++){//1.创建管道int pipefd[2] = {0};int n = pipe(pipefd);if(n < 0)return false;//2.创建进程pid_t pid = fork();if(pid < 0){//创建进程失败return false;}else if(pid == 0){//子进程//我们要求的是父进程去写,子进程去读,所以我们要关掉不要用的读写端,0是读,1是写close(pipefd[1]);//子进程关掉写//建立子进程,那么子进程就要工作,先不弄太复杂,就简单写个函数Work(pipefd[0]);close(pipefd[0]);//到最后都要关掉}else{//父进程//关掉读close(pipefd[0]);//父进程创建子进程之后,我们要给子进程建立一个通信信道_cm.BuildChannel(pipefd[1],pid);close(pipefd[1]);//到最后都要关掉}}return true;}~ProcessPool(){}
private:ChannelManage _cm;int _process_num;//管道数量
};#endif
  • 创建管道,使用pipe函数(记得要包的头文件哈!),然后创建进程,子进程负责完成任务,父进程负责为子进程开通管道,自然而然地,有了BuildChannel函数。
  • 我们看到管理管道类里面的BuildChannel函数,我们虽然使用了vector的emplace_back函数,可以不用创建临时对象的,但是我们应该明白底层逻辑是什么样的,
  • 底层逻辑就是创建了一个channel(管道)临时对象,将他尾插到_cm里面之后,再将其销毁。而创建管道是需要父进程的写入端和子进程的pid。

我们创建了子进程,并为他开创了信道之后,就可以得到命令,然后去执行命令了,但是要执行命令,也要选择合适的子进程,让合适的子进程去执行任务。所以下一步需要解决的就是选择合适的子进程执行命令。

那么,怎么挑选合适的子进程呢?有一种常用的方法,轮询这个方法就是从第一个子进程开始,依次往下执行,直到结束,然后再从第一个开始,我们就使用轮询的方法吧。

#ifndef _PROCESS_POOL_HPP_
#define _PROCESS_POOL_HPP_#include<iostream>
#include<vector>
#include<unistd.h>
#include<cstdlib>//管道类,先描述,建立信道
class channel
{
public:channel(int fd,pid_t pid):_wfd(fd),pid(pid){std::cout << "channel-" <<std::to_string(_wfd)+std::to_string(pid);}~channel(){}private:int _wfd;pid_t pid;std::string name;};const int gdefaultnum = 5;//管道数量,暂时定为5个//管道管理类,再组织
class ChannelManage
{
public:ChannelManage():next(0){}void BuildChannel(int wfd,pid_t pid ){//vector的一个函数,不需要构建临时对象,直接就可以尾插到vector里面_Channels.emplace_back(wfd,pid);}//挑选合适的子进程去执行命令channel& Select(){//轮询auto& c = _Channels[0];next++;next %= _Channels.size();return c;}~ChannelManage(){}
private:std::vector<channel> _Channels;int next;
};//进程池类
class ProcessPool
{
public:ProcessPool():_process_num(gdefaultnum){}void Work(int rfd){std::cout<<"子进程工作" <<std::endl;}bool Start(){for (int i = 0; i < _process_num; i++){//1.创建管道int pipefd[2] = {0};int n = pipe(pipefd);if(n < 0)return false;//2.创建进程pid_t pid = fork();if(pid < 0){//创建进程失败return false;}else if(pid == 0){//子进程//我们要求的是父进程去写,子进程去读,所以我们要关掉不要用的读写端,0是读,1是写close(pipefd[1]);//子进程关掉写//建立子进程,那么子进程就要工作,先不弄太复杂,就简单写个函数Work(pipefd[0]);close(pipefd[0]);//到最后都要关掉}else{//父进程//关掉读close(pipefd[0]);//父进程创建子进程之后,我们要给子进程建立一个通信信道_cm.BuildChannel(pipefd[1],pid);close(pipefd[1]);//到最后都要关掉}}return true;}void Run(){auto &c = _cm.Select();}~ProcessPool(){}
private:ChannelManage _cm;int _process_num;//管道数量
};#endif

我们已经完成了开创信道,选择子进程,接下来的任务就是写向写入端写入命令。

class channel
{
public:channel(int fd,pid_t pid):_wfd(fd),pid(pid){std::cout << "channel-" <<std::to_string(_wfd)+std::to_string(pid);}void Send(int code){int n = write(_wfd,&code,sizeof(code));(void)n;}~channel(){}private:int _wfd;pid_t pid;std::string name;};
void Run(){int task_code = 0;//选择一个子进程auto &c = _cm.Select();//向写入端发送命令c.Send(task_code);}

上面我们还不知道需要发送什么命令,所以就随便设置了一个0,现在我们需要一套完整的命令了,直接写一个task.hpp文件,设置一套完整的命令。下面是task.hpp的代码编写

#pragma once#include<iostream>
#include<vector>
#include<ctime>typedef void (*task_t)();////////////////执行任务///////////////////////////////////////////////////////
void Printlog()
{std::cout << "我是一个打印日志的任务"<<std::endl;
}void Download()
{std::cout << "我是一个下载的任务"<<std::endl;
}void Upload()
{std::cout << "我是一个上传的任务"<<std::endl;
}///////////////////////////////////////////////////////////////////////////////class TaskManager
{
public:TaskManager(){srand(time(nullptr));}//将执行函数放进去void Register(task_t t){_tasks.push_back(t);}int Code(){return rand() % _tasks.size();}void Execute(int code){if(code >=0 && code < _tasks.size()){_tasks[code];}}~TaskManager(){}private:std::vector<task_t> _tasks;
};

写了任务表了,那就要让子进程在读取管道信息的时候接收到这个任务命令,那么将变动Work函数,让他工作。下面是将命令融合到整个程序里去的代码

#ifndef _PROCESS_POOL_HPP_
#define _PROCESS_POOL_HPP_#include<iostream>
#include<vector>
#include<unistd.h>
#include<cstdlib>
#include"task.hpp"//管道类,先描述,建立信道
class channel
{
public:channel(int fd,pid_t pid):_wfd(fd),pid(pid){std::cout << "channel-" <<std::to_string(_wfd)+std::to_string(pid);}void Send(int code){int n = write(_wfd,&code,sizeof(code));(void)n;}~channel(){}int getwfd(){return _wfd; }pid_t getpid(){return pid;}std::string getname(){return name;}private:int _wfd;pid_t pid;std::string name;};const int gdefaultnum = 5;//管道数量,暂时定为5个//管道管理类,再组织
class ChannelManage
{
public:ChannelManage():next(0){}void BuildChannel(int wfd,pid_t pid ){//vector的一个函数,不需要构建临时对象,直接就可以尾插到vector里面_Channels.emplace_back(wfd,pid);}//挑选合适的子进程去执行命令channel& Select(){//轮询auto& c = _Channels[0];next++;next %= _Channels.size();return c;}~ChannelManage(){}
private:std::vector<channel> _Channels;int next;
};//进程池类
class ProcessPool
{
public:ProcessPool():_process_num(gdefaultnum){//注册任务_tm.Register(Printlog);_tm.Register(Download);_tm.Register(Upload);}void Work(int rfd){while(true){int code = 0;size_t n = read(rfd,&code,sizeof(code));//从读端读到了code,之前父进程写进管道的任务码if(n > 0){if(n != sizeof(code)){continue;}//读到规范的了std::cout << "进程: "<<getpid()<<"收到一个任务码: "<<code<<std::endl;_tm.Execute(code);}else if(n == 0){std::cout<<"子进程退出"<<std::endl;}else{std::cout<<"读取错误"<<std::endl;}}}bool Start(){for (int i = 0; i < _process_num; i++){//1.创建管道int pipefd[2] = {0};int n = pipe(pipefd);if(n < 0)return false;//2.创建进程pid_t pid = fork();if(pid < 0){//创建进程失败return false;}else if(pid == 0){//子进程//我们要求的是父进程去写,子进程去读,所以我们要关掉不要用的读写端,0是读,1是写close(pipefd[1]);//子进程关掉写//建立子进程,那么子进程就要工作,先不弄太复杂,就简单写个函数Work(pipefd[0]);close(pipefd[0]);//到最后都要关掉}else{//父进程//关掉读close(pipefd[0]);//父进程创建子进程之后,我们要给子进程建立一个通信信道_cm.BuildChannel(pipefd[1],pid);close(pipefd[1]);//到最后都要关掉}}return true;}void Run(){int task_code = _tm.Code();//随机生成的一个code//选择一个子进程auto &c = _cm.Select();std::cout <<"选择了一个子进程: "<<c.getname() <<std::endl;//向写入端发送命令c.Send(task_code);std::cout <<"发送了一个任务码: "<<task_code <<std::endl;}~ProcessPool(){}
private:ChannelManage _cm;int _process_num;//管道数量TaskManager _tm;//命令管理
};#endif

尤其可以注意一下Work的变动。

上面所实现的就是我们需要的功能,现在我们需要实现一些关闭和等待的功能,我们的管道和进程都是需要实现关闭功能的。

    void Close(int wfd){close(_wfd);}void Wait(){pid_t id = waitpid(pid,nullptr,0);(void)id;}
    void StopSubProcess(){for(auto& channel : _Channels){channel.Close();std::cout<<"关闭:"<<channel.getname()<<std::endl;}}void WaitSubProcess(){for(auto& channel : _Channels){channel.Wait();std::cout<<"回收子进程" <<std::endl;}}
    void Stop(){_cm.StopSubProcess();_cm.WaitSubProcess();}

这就是三个类分别的关闭等待代码,从下往上,是不是感受到了层层调用?

至此,我们的代码已经写完了,我会将完整代码包括测试代码链接贴在下面,大家自行取用。

https://gitee.com/i-still-want-to-be-an-npc/vscode-code

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

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

相关文章

【tips】css模仿矢量图透明背景

就像棋盘格background-image: linear-gradient(45deg, #f0f0f0 25%, transparent 25%), linear-gradient(-45deg, #f0f0f0 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #f0f0f0 75%), linear-gradient(-45deg, transparent 75%, #f0f0f0 75%);background-…

visual studio 历史版本安装

visual studio 历史版本安装 链接&#xff1a;Visual Studio 版本路线图 说明&#xff1a;该页面提供历史版本的发布说明及下载链接&#xff08;需滚动至页面底部查找相关版本&#xff09;。例如&#xff0c;2022 版本可能包含 17.0 至 17.14 等子版本&#xff0c;用户可根据需…

微软推出“愤怒计划“:利用AI工具实现恶意软件自主分类

微软周二宣布推出一款能够自主分析并分类软件的人工智能&#xff08;AI&#xff09;代理系统&#xff0c;旨在提升恶意软件检测能力。这款基于大语言模型&#xff08;LLM&#xff09;的自主恶意软件分类系统目前仍处于原型阶段&#xff0c;被微软内部代号命名为"愤怒计划&…

SOLIDWORKS Electrical:实现真正意义上的机电协同设计

随着市场的发展&#xff0c;企业面临两个方面的挑战&#xff1a;从业务和市场方面来看&#xff0c;为了在竞争中取得更大优势&#xff0c;需要更高质量的产品&#xff0c;较低的成本并缩短产品上市周期&#xff1b;从设计和技术方面来看&#xff0c;产品的集成度越来越高&#…

MySql_忘记了root密码怎么办

《MySql_忘记了root密码怎么办》在忘记root密码的时候&#xff0c;可以按以下步骤处理&#xff08;以windows为例&#xff09;。_1) 关闭正在运行的MySQL服务。_2) 打开DOS窗口&#xff0c;转到mysql\bin目录。_3) 输入mysqld –skip-grant-tables 回车。–skip-grant-tables 的…

wstool和catkin_tools工具介绍

好的&#xff0c;我们来详细介绍一下 python3-wstool 和 python3-catkin-tools 这两个在 ROS (Robot Operating System) 开发中非常重要的工具&#xff0c;以及它们之间的关系。 首先&#xff0c;python3- 这个前缀表示这些是针对 Python 3 的软件包版本&#xff0c;这在现代 R…

吴恩达 深度学习笔记

最近在看吴恩达深度学习系列课程&#xff0c;简单做一个基本框架笔记。 如感兴趣或了解更多内容&#xff0c;推荐看原课程 以前也做过一些与机器学习和深度学习有关的笔记&#xff0c;过分重复的就一笔带过了。 01 第一门课 神经网络和深度学习 1.1 第一周&#xff1a;深度学习…

2025数字马力一面面经(社)

2025数字马力一面面经&#xff08;社&#xff09; 日常自我介绍js数据类型有哪些&#xff08;报完菜名后简单分析了一下使用引用类型&#xff09;谈谈对const、var、let的理解&#xff08;变量提升、let和const的主要区别、使用const命名引用类型的时可以对引用类型进行操作&am…

PyQt 中 pyqtSignal 的使用

目录 基本用法 示例代码 关键特性 常见用途 一、信号的定义规则 二、完整用法步骤 1. 导入必要模块 2. 定义带信号的类 3. 定义接收信号的槽函数 4. 连接信号与槽 5. 发射信号 6. 断开连接(可选) 三、高级特性 1. 跨线程通信 2. 信号连接方式 3. 信号与匿名函数 4. 信号转发 …

使用Python验证常见的50个正则表达式

什么是正则表达式&#xff1f;正则表达式&#xff08;Regular Expression&#xff09;通常被用来检索、替换那些符合某个模式(规则)的文本。此处的Regular即是规则、规律的意思&#xff0c;Regular Expression即“描述某种规则的表达式”之意。本文收集了一些常见的正则表达式用…

Redis是单线程性能还高的原因

Redis是单线程Redis单线程是指Redis的网络IO和键值对读写是由一个线程完成的,其他功能还是使用多线程执行Redis主干业务使用单线程的原因Redis本质就是一个大的共享资源,共享资源是需要对其进行并发控制的,即使增加了线程,大部分线程也是在等待互斥锁,并行变串行,而且还需要进行…

若依前后端分离版学习笔记(七)—— Mybatis,分页,数据源的配置及使用

一 Mybatis 1、Maven依赖 在ruoyi父项目的pom文件中有一个分页插件的依赖 <!-- pagehelper 分页插件 --> <dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version&…

灌区信息化智能管理系统解决方案

一、方案背景 灌区作为农业灌溉的重要基础设施&#xff0c;承担着保障粮食安全和促进农业可持续发展的关键作用。然而&#xff0c;传统灌区管理方式普遍存在信息孤岛、数据滞后、调度不精准等问题&#xff0c;导致水资源浪费和管理效率低下。在此背景下&#xff0c;灌区信息化智…

软件包管理、缓存、自定义 YUM 源

1. 软件包管理是啥 你可以把软件包管理器理解成 Linux 的“应用商店 安装工人”&#xff1a; 应用商店&#xff1a;帮你找到软件&#xff08;包&#xff09;安装工人&#xff1a;帮你下载安装、配置、升级、卸载管理账本&#xff1a;记录系统里都安装了啥、版本号是多少、依赖…

Pthon 本质详解

理解 Python 的本质&#xff0c;不能仅仅停留在“它是一门编程语言”这个层面&#xff0c;而要深入其设计哲学、核心机制、以及它在编程世界中所扮演的角色。 可以把 Python 的本质概括为一句话&#xff1a;Python 的本质是一种以“简洁优雅、易于读写”为核心设计哲学&#xf…

在Word文档中用键盘直接移动(复制)内容

如何快速在Word文档中剪切或复制内容到本文档的其他位置&#xff1f;不用剪切或复制&#xff0c;再粘贴&#xff0c;只需要先选中内容&#xff0c;然后按下F2&#xff08;ShiftF2&#xff09;剪切&#xff08;复制&#xff09;内容&#xff0c;再把光标放到目标位置按下回车键就…

VRTE 的应用程序部署到Ubuntu上 报错:bash: ./rb_exmd: No such file or directory

&#x1f6e0;️ 如何在 Ubuntu 上部署 VRTE 3.5 的 AraCM_IPC 应用程序在将 VRTE 3.5 的 AraCM_IPC 应用部署到 Ubuntu 系统时&#xff0c;可能会遇到运行失败的问题&#xff0c;提示类似&#xff1a;bash: ./rb_exmd: No such file or directory这通常并非文件不存在&#xf…

WD5202 非隔离降压转换芯片,220V降5V,输出电流80MA

解锁高效电源新境界&#xff1a;WD5202 非隔离降压转换芯片在当今电子设备飞速发展的时代&#xff0c;高效、稳定且低成本的电源解决方案至关重要。WD5202 作为一款卓越的非隔离降压转换芯片&#xff0c;正以其独特的性能和广泛的适用性&#xff0c;在众多领域崭露头角&#xf…

库函数版独立按键用位运算方式实现(STC8)

位运算&#xff1a;更加简便&#xff0c;单片机的内存就小&#xff0c;占的内存空间小一点案例&#xff1a; #include "GPIO.h" #include "Delay.h" #include "UART.h" // 串口配置 UART_Configuration #include "NVIC.h" // 中断…

RA4M2_MINI开发(15)----配置RTC时钟及显示时间

RA4M2_MINI开发.15--配置RTC时钟及显示时间 概述视频教学样品申请硬件准备参考程序源码下载新建工程工程模板保存工程路径芯片配置工程模板选择时钟设置SWD调试口设置UART配置UART属性配置设置e2studio堆栈e2studio的重定向printf设置R_SCI_UART_Open()函数原型回调函数user_ua…