Linux进程间通信----简易进程池实现

article/2025/8/11 12:45:40

进程池的模拟实现

1.进程池的原理:

是什么

进程池是一种多进程编程模式,核心思想是先创建好一定数量的子进程用作当作资源,这些进程可以帮助完成任务并且重复利用,避免频繁的进程的创建和销毁的开销。

下面我们举例子来帮助理解:

完成任务需要工作人员,在主任务执行的途中源源不断的有新的支线任务来临。比如校园社团展览大会的场地布置是主任务,但是不同的场地要有不同的布置任务布置结构,在整理真个场地的途中你需要帮手去帮你解决小任务,社团A来发任务,你现场去找一个人让他去做A任务,社团B来发放任务,你又得去找一个人去完成B任务,效率极低。这时候你提前找好了10个帮手,让他们待命,来了任务直接将帮手派发出去,帮手完成任务后回到等待任务队列,整体效率就提高了很多并且免去了频繁找人的过程。

上面的故事里,帮手就是组成进程池的子进程,你作为父进程需要组织并管理这些子进程,让他们去帮你完成任务。

为什么

为什么要有进程池?操作系统在创建进程时要给进程分配内存等资源,在任务短频率高的情况下消耗极高。同时子进程可以反复利用,可以将这些子进程的创建成本分摊到多次任务中。

怎么做

首先创建进程池就需要预设好一定数量的进程,第一步就是创建一定数量的子进程

第二步,为了能让子进程帮我们完成任务,我们需要和子进程通信,利用上次讲到的管道

第三步,任务的发布和负载均衡,发布任务的时候尽量让每个进程都被平均的使用到,发挥进程池的优势。

实现类似下图的结构:

代码实现:

第一步,完成任务一:所需数量的创建管道和子进程

#include <iostream>
#include <vector>
#include <unistd.h>using namespace std;
#define PIPEERRO 1int num = 5; // 全局变量,表示进程池内的进程数量int main()
{for (int i = 0; i < num; i++){// 1.创建管道和子进程int pipefd[2];int n = pipe(pipefd);if (n < 0){return PIPEERRO;}//2.创建子进程pid_t id=fork();if(id==0){//子进程代码//子进程负责读取任务 ,关闭写端close(pipefd[1]);//读取任务//执行任务//退出exit(0);}//父进程代码//父进程负责写任务,关闭读端close(pipefd[0]);}return 0;
}

 框架搭建完毕后,我们需要对创建出来的管道和子进程进行管理,管理的6字真言:先描述再组织

为了管理好我们创建的这么多管道和子进程我们创建一个管道类Channel去描述管道和子进程的关系,然后用vector去组织他们。

创建的所有管道都被放入channels vector数组里管理起来,以后我们想对单个子进程发送任务就只需要发给对应的数组成员就行了,操作十分方便。

现在我们完成了第一步,写一个假的work函数看看能不能工作吧:

void work()
{cout<<"I am child process: "<<getpid()<<endl;
} int main()
{vector<Channel> channels;for (int i = 0; i < num; i++){// 1.创建管道和子进程int pipefd[2];int n = pipe(pipefd);if (n < 0){return PIPEERRO;}//2.创建子进程pid_t id=fork();if(id==0){//子进程代码//子进程负责读取任务 ,关闭写端close(pipefd[1]);//读取任务//执行任务work();//退出exit(0);}//父进程代码//父进程负责写任务,关闭读端close(pipefd[0]);channels.emplace_back(id,pipefd[1]);}return 0;
}

执行结果:

十分成功,接下来我们去实现第二步:任务的发放

我们首先实现几种不同的任务函数,利用回调函数的方式让我们的子进程随机执行任务,为了完成这一步我们利用数组将task(任务)函数用数组管理起来,形成任务清单,任务的发放方式也改为向管道内发送task函数数组的下标,即发放task清单序号,子进程根据从管道内收到的序号去执行清单上对应task。

为了统一接口,我们定义一个进程池类统一接口去管理操作函数

class ProcessPool
{
public:ProcessPool(int num) : sub_proc_num(num){}int CreateProcessPool(){//vector<Channel> channels;  类成员变量无需再次定义for (int i = 0; i < num; i++){// 1.创建管道和子进程int pipefd[2];int n = pipe(pipefd);if (n < 0){return PIPEERRO;}// 2.创建子进程pid_t id = fork();if (id == 0){// 子进程代码// 子进程负责读取任务 ,关闭写端close(pipefd[1]);// 读取任务// 执行任务// 退出exit(0);}// 父进程代码// 父进程负责写任务,关闭读端close(pipefd[0]);channels.emplace_back(id, pipefd[1]);}}~ProcessPool(){}private:vector<Channel> channels;int sub_proc_num;
};int main()
{ProcessPool * pp=new ProcessPool(num);//num 个进程的进程池pp->CreateProcessPool();return 0;
}

接下来:创建一个头文件专门放置任务函数

#pragma once#include<iostream>
#include <unistd.h>
using namespace std;
typedef void(*work_t)();//执行任务总接口函数指针
typedef void(*task_t)();//任务函数指针,用于管理任务函数//task函数实现
void Singing()
{cout<<"I am singing.... lalala~"<<endl;
}void Dancing()
{cout<<"I am dancing.... siusiusiu~"<<endl;
}void Playing()
{cout<<"I am playing piano.... DoReMi~"<<endl;
}uint32_t NextTask()
{return rand()%3;//task数组下标
}task_t task[3]{Singing,Dancing,Playing};//数组管理task函数void worker()//总接口函数
{while(true){uint32_t command_code;//任务码ssize_t byte_read = read(0,&command_code,sizeof(command_code));//读取管道内容(任务码)if(byte_read == sizeof(command_code)){if(command_code>3)continue;task[command_code]();//任务码用作下标}}
}

 实现任务清单,同时给出work总接口,兼具读取任务码和执行动作。

说明:

为什么死循环?死循环的目的是让子进程反复接受任务反复执行任务,这样能最大化利用进程池特性。

为什么command_code>3就continue?为了让子进程收到非法任务码时也能继续执行不影响后续任务执行。

主程序代码只需修改几个地方:

pp->CreateProcessPool(worker);//传总接口函数进去
int CreateProcessPool(work_t worker){// vector<Channel> channels;  类成员变量无需再次定义for (int i = 0; i < num; i++){// 1.创建管道和子进程int pipefd[2];int n = pipe(pipefd);if (n < 0){return PIPEERRO;}// 2.创建子进程pid_t id = fork();if (id == 0){// 子进程代码// 子进程负责读取任务 ,关闭写端close(pipefd[1]);// 读取任务dup2(pipefd[0], 0); // 重定向,将从stdin读取重定向到从管道读端读取// 执行任务worker(); // 总接口内有真正的读取管道操作// 退出exit(0);}// 父进程代码// 父进程负责写任务,关闭读端close(pipefd[0]);channels.emplace_back(id, pipefd[1]);}return 0;}

 说明:

因为在create函数里面进行了重定向,所有worker接口里的read是从0也就是stdin里读取

准备好了任务码的读取,接下来我们来完成任务码的发放:

注意两点:

        1.任务码随机发方

        2.子进程的轮询调度(为了让所有进程分摊任务)

// 任务码发放while (true){// 选择任务码uint32_t code = NextTask();// 选择管道int index = pp->Select_Channel();// 发送任务码pp->SendCode(index,code);sleep(1);}
    int Select_Channel() // 轮询选择管道{static int next = 0; // static变量只定义一次int c = next;next++;next %= channels.size(); // next只能在channels的下标中循环return c;}void SendCode(int index, int code) // 向管道写入任务码{cout << "sending code : " << code << " to " << channels[index].getid()<< " " << channels[index].getname() << endl;write(channels[index].getfd(), &code, sizeof(code)); // 向管道写入任务码}
uint32_t NextTask()
{return rand()%3;//task数组下标
}

 接下来看执行结果:

非常完美,任务自动不停的发送给每个管道内,由不同的子进程执行。

完整代码:

processpool.cc:

#pragma once#include<iostream>
#include <unistd.h>
using namespace std;
typedef void(*work_t)();//执行任务总接口函数指针
typedef void(*task_t)();//任务函数指针,用于管理任务函数//task函数实现
void Singing()
{cout<<"I am singing.... lalala~"<<endl;
}void Dancing()
{cout<<"I am dancing.... siusiusiu~"<<endl;
}void Playing()
{cout<<"I am playing piano.... DoReMi~"<<endl;
}uint32_t NextTask()
{return rand()%3;//task数组下标
}task_t task[3]{Singing,Dancing,Playing};//数组管理task函数void worker()//总接口函数
{while(true){uint32_t command_code;//任务码ssize_t byte_read = read(0,&command_code,sizeof(command_code));//读取管道内容(任务码)if(byte_read == sizeof(command_code)){if(command_code>3)continue;task[command_code]();//任务码用作下标}}
}

Task.hpp:

#pragma once#include <iostream>
#include <unistd.h>using namespace std;typedef void (*work_t)(int);
typedef void (*task_t)();void Singing()
{cout << "I am singing.... lalala" << endl;
}void Dancing()
{cout << "I am dancing.... hahaha" << endl;
}void Playing()
{cout << "I am playing piano.... doremi~" << endl;
}task_t task[3] = {Singing, Dancing, Playing};uint32_t Select_Task()
{return rand()%3;
}void worker(int task_num)
{while (task_num){uint32_t commandcode;ssize_t byte_read = read(0, &commandcode, sizeof(commandcode));if (byte_read == sizeof(commandcode)){if (commandcode > 3)continue;task[commandcode]();task_num--;}}
}


http://www.hkcw.cn/article/TypLPOcPfv.shtml

相关文章

【Oracle】安装单实例

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Oracle 文章目录 1. 安装前的准备工作1.1 硬件和系统要求1.2 检查系统环境1.3 下载Oracle软件 2. 系统配置2.1 创建Oracle用户和组2.2 配置内核参数2.3 配置用户资源限制2.4 安装必要的软件包 3. 目录结构和环境变量3.1 创建Ora…

Pyecharts 库的概念与函数

基本概念 Pyecharts 是一个基于 ECharts 的 Python 数据可视化库&#xff0c;具有以下特点&#xff1a; 基于 ECharts&#xff1a;底层使用百度开源的 ECharts 图表库 多种图表类型&#xff1a;支持折线图、柱状图、饼图、散点图、地图等多种图表 交互式&#xff1a;生成的图…

【深入详解】C语言内存函数:memcpy、memmove的使用和模拟实现,memset、memcmp函数的使用

目录 一、memcpy、memmove使用和模拟实现 &#xff08;一&#xff09;memcpy的使用和模拟实现 1、代码演示&#xff1a; &#xff08;1&#xff09;memcpy拷贝整型 &#xff08;2&#xff09;memcpy拷贝浮点型 2、模拟实现 &#xff08;二&#xff09;memmove的使用和模…

设计模式——责任链设计模式(行为型)

摘要 责任链设计模式是一种行为型设计模式&#xff0c;旨在将请求的发送者与接收者解耦&#xff0c;通过多个处理器对象按链式结构依次处理请求&#xff0c;直到某个处理器处理为止。它包含抽象处理者、具体处理者和客户端等核心角色。该模式适用于多个对象可能处理请求的场景…

软件的兼容性如何思考与分析?

软件功能的兼容性是指软件在实现功能的时候&#xff0c;能够与其他软件、硬件、系统环境以及数据格式等相互协作、互不冲突&#xff0c;并且能够正确处理不同来源或不同版本的数据、接口和功能模块的能力。它确保软件在多种环境下能够正常运行&#xff0c;同时与其他系统和用户…

C++ —— STL容器——string类

1. 前言 本篇博客将会介绍 string 中的一些常用的函数&#xff0c;在使用 string 中的函数时&#xff0c;需要加上头文件 string。 2. string 中的常见成员函数 2.1 初始化函数 string 类中的常用的初始化函数有以下几种&#xff1a; 1. string() …

DFS每日刷题

目录 P1605 迷宫 P1451 求细胞数量 P1219 [USACO1.5] 八皇后 Checker Challenge P1605 迷宫 #include <iostream> using namespace std; int n, m, t; int a[20][20]; int startx, starty, endx, endy; bool vis[20][20]; int res; int dx[] {0, 1, 0, -1}; int dy[]…

USART 串口通信全解析:原理、结构与代码实战

文章目录 USARTUSART简介USART框图USART基本结构数据帧起始位侦测数据采样波特率发生器串口发送数据 主要代码串口接收数据与发送数据主要代码 USART USART简介 一、USART 的全称与基本定义 英文全称 USART&#xff1a;Universal Synchronous Asynchronous Receiver Transmi…

C# winform 教程(一)

一、安装方法 官网下载社区免费版&#xff0c;在线下载安装 VS2022官网下载地址 下载后双击启动&#xff0c;选择需要模块&#xff08;net桌面开发&#xff0c;通用window平台开发&#xff0c;或者其他自己想使用的模块&#xff0c;后期可以修改&#xff09;&#xff0c;选择…

ZLG ZCANPro,ECU刷新,bug分享

文章目录 摘要 📋问题的起因bug分享 ✨思考&反思 🤔摘要 📋 ZCANPro想必大家都不陌生,买ZLG的CAN卡,必须要用的上位机软件。在汽车行业中,有ECU软件升级的需求,通常都通过UDS协议实现程序的更新,满足UDS升级的上位机要么自己开发,要么用CANoe或者VFlash,最近…

Matlab作图之 subplot

1. subplot(m, n, p) 将当前图形划分为m*n的网格&#xff0c;在 p 指定的位置创建坐标轴 matlab 按照行号对子图的位置进行编号 第一个子图是第一行第一列&#xff0c;第二个子图是第二行第二列......... 如果指定 p 位置存在坐标轴&#xff0c; 此命令会将已存在的坐标轴设…

【STM32F1标准库】理论——外部中断

目录 一、中断介绍 二、外部引脚EXTI申请的中断 三、外部中断的适用场景 四、其他注意事项 一、中断介绍 STM32可以触发中断的外设有外部引脚(EXTI)、定时器、ADC、DMA、串口、I2C、SPI等 中断同一由NVIC管理 n表示一个外设可能同时占用多个中断通道 优先级的值越小优先…

SAP学习笔记 - 开发18 - 前端Fiori开发 应用描述符(manifest.json)的用途

上一章讲了 Component配置&#xff08;组件化&#xff09;。 本章继续讲Fiori的知识。 目录 1&#xff0c;应用描述符(Descriptor for Applications) 1&#xff09;&#xff0c; manifest.json 2&#xff09;&#xff0c;index.html 3&#xff09;&#xff0c;Component.…

定时任务:springboot集成xxl-job-core(一)

springboot:2.7.2 xxl-job-core: 2.3.0 一、集成xxl-job 1. 在gitee上下载xxl-job项目 git clone https://gitee.com/xuxueli0323/xxl-job.git 2. 执行以下目录下的sql /xxl-job-2.3.0/doc/db/tables_xxl_job.sql 3. 在xxl-job-admin的项目中配置数据库信息 ### xxl-job, data…

【STM32开发板】接口部分

一、USB接口 可以看到USBP和USBN与PA12,PA11引脚相接,根据协议&#xff0c;需要添加上拉电阻 二、ADC和DAC 根据原理图找到可以作为ADC和DAC的引脚 ADC和DAC属于模拟部分的&#xff0c;所以要接模拟地 三、指示灯电路 找几个通用的引脚&#xff0c;因为单片机的灌电流比拉电流…

阻塞队列BlockingQueue解析

阻塞队列是一个支持两个附加操作的队列。这两个附加的操作支持阻塞的插入和移除的方法。 阻塞插入&#xff1a;当队列满的时候&#xff0c;队列会阻塞插入元素的线程&#xff0c;直到队列不满。 阻塞移除&#xff1a;当队列空的时候&#xff0c;队列会阻塞移除元素的线程&…

[Redis] Redis命令在Pycharm中的使用

初次学习&#xff0c;如有错误还请指正 目录 String命令 Hash命令 List命令 set命令 SortedSet命令 连接pycharm的过程见&#xff1a;[Redis] 在Linux中安装Redis并连接桌面客户端或Pycharm-CSDN博客 redis命令的使用见&#xff1a;[Redis] Redis命令&#xff08;1&#xf…

车载控制器的“机电一体化”深度集成

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 所谓鸡汤&#xff0c;要么蛊惑你认命&#xff0c;要么怂恿你拼命&#xff0c;但都是回避问题的根源&…

PINN模型相关原理

PINN模型相关原理 目录 PINN模型相关原理原本的物理界的利用神经网络的参数估计PINN 的原理介绍一、基本思想二、PINN 的损失函数三、自动微分&#xff08;Autodiff&#xff09;四、PINN 的优势与挑战 原本的物理界的利用神经网络的参数估计 原本物理界需要确定一个三维流体&a…

计算机基础——宏病毒防御与网络技术

文章目录 宏病毒详解与防范措施宏病毒简介宏病毒的特点宏病毒的传播途径宏病毒的防范措施宏病毒的检测与清除 自治计算机与自治系统解析什么是自治计算机&#xff1f;技术特点 自治系统&#xff08;Autonomous System, AS&#xff09;特点&#xff1a;自治系统类型 总结&#x…