Linux——进程池
前言:大佬写博客给别人看,菜鸟写博客给自己看,我是菜鸟。
1.实现思路
思路:通过创建匿名管道,来实现父子进程之间的通信
注1:父写,子读
注2:匿名管道只能用来进行具有血管关系的进程进行进程间的通信
①.父进程通过for循环创建五个管道和子进程,管道与子进程间一一对应。五个子进程处于阻塞状态,等待父进程向管道内写入数据。
注:父进程在创建子进程时,子进程会继承父进程的所有数据,包括管道。
②.通过自定义类Channel,管理类成员 pipefd[0]和子进程的pid;
注:当管道创建时,同一管道间的pipe[0]和pipe[1]是一一对应的,即假设我向管道1写入,那么子进程则会从管道1读取数据;
③.大体结构——创建三个自定义类
Ⅰ.管道(Channel):
成员包含:pipe[0]以及对应子进程,建立管道和子进程间一一对应的关系;
函数包含:向管道写入数据、管道关闭、等待子进程
Ⅱ.管道管理(Channel),
成员包含:通过vector<Channel> _channel 对管道数据进行管理;
函数包含:管道选取、管道关闭、子进程回收。
Ⅲ.进程池(ProcessPool),
成员包含:管道管理实例化对象_cm、任务管理实例化对象_tm、进程数量/管道数量;
函数包含:进程池初始化/创建、子进程阻塞等待管道写入并执行、执行任务
注:父进程创建管道后(该管道会返回文件描述符,假设为3,4),再创建子进程,子进程能够进程父进程的管道。因为父进程负责写入,因此需要把读取端口关掉,假设关闭3。这样父进程就通过文件描述符4向管道写入数据。而整个管道的创建过程是循环创建的,当父进程再次创建管道时,此时文件描述符为3、5,重复刚才的操作,这样父进程就能通过文件描述符5向管道写入数据,直至所有管道创建完毕。
问:既然子进程会继承父进程,那在第二次或者后面的循环过程中,子进程会不会继承父进程的写端?从而实现子进程向管道写入?
答:会!
问:这样子会出现什么问题?
答:当我们通过父进程关闭管道,回收子进程时,你以为管道已经关闭了,但实际上还有子进程能够向这个管道进行写入操作,实际上管道未关闭,子进程并不会退出,而是会阻塞。
解决措施1:从后往前关闭管道,对于最后一个管道而言,没有子进程能够向其写入,父进程是唯一的写入端,因此关闭这个管道后,子进程能够顺利关闭,因为信道关闭了,子进程也就退出了,而当前子进程能够向上一个管道写入,但是当前子进程退出了,因此上一个管道的写入端就少了一个(实际上关闭当前子进程后,上一个管道的写入端就只剩父进程一个了,这样循环向上,就能够确保通过父进程能够关闭所有的管道,从而使得所有的子进程关闭而非处于阻塞状态)
解决措施2:创建子进程后,在处理子进程时,遍历vector<Channel> _channel,因为内部存储了所有管道对应的写端,然后在子进程内调用 _channel.Close();关闭所有写端
问:这样调用,子进程是否会关闭父进程的管道?
答:不会,父子进程相互独立。父子进程如果要修改原始数据会发生写实拷贝。
匿名管道的创建方式:
int pipefd[2] = {0};
int n = pipe(pipefd);
其中
pipefd[0]为输入\向管道读
pipefd[1]为输出\从管道写
2.实现代码
2.1Main.cc
主要分三部分:
①.进程池初始化
②.执行任务
③.回收、结束进程池
int main()
{// 创建进程池对象ProcessPool pp(gdefaultnum);// 启动进程池pp.Start();// 自动派发任务int cnt = 10;while(cnt--){pp.Run();sleep(1);}// 回收,结束进程池pp.Stop();return 0;
}
2.2进程池(ProcessPool)
2.2.1进程池大体成员以及函数
class ProcessPool
{
public:ProcessPool(int num) : _process_num(num){//进程池创建时,等级需要执行的任务,这部分通过另一个自定义类来实现}void Work(int rfd){//子进程阻塞等待父进程向管道写入数据,读取后执行相应任务}bool Start(){//进程池初始化}void Run(){//执行任务}void Stop(){//关闭管道、回收子进程}~ProcessPool(){}private:ChannelManager _cm;int _process_num;TaskManager _tm;
};
2.2.2任务登记/管理
TaskManager自定义类:
通过函数指针/随机函数来实现任务的随机选取
typedef void (*task_t)(); //函数指针void Task1() {std::cout << "印日志的任务" << std::endl; }void Task2() {std::cout << "下载的任务" << std::endl; }void Task3() {std::cout << "上传的任务" << std::endl; }class TaskManager { public:TaskManager(){srand(time(nullptr));//生成随机数种子}void Register(task_t t){_tasks.push_back(t);//将当前函数指针插入到vector数组中}int Code(){return rand() % _tasks.size();//返回一个随机数,大小不超过最大任务数量-1}void Execute(int code) //根据code执行相应任务{if(code >= 0 && code < _tasks.size()){_tasks[code]();//回调函数}}~TaskManager(){} private:std::vector<task_t> _tasks; };
开始时将所有任务插入vector<task_t> _tasks中:
ProcessPool(int num) : _process_num(num){_tm.Register(Task1);_tm.Register(Task2);_tm.Register(Task3);}注:这部分可以自定义,写你想要实现的任务即可。
2.2.3进程池初始化
大致思路为:外循环循环五次,
①.每次创建一个管道
②.每次创建一个子进程
③.关闭父子进程相应的管道,父写子读
④.子进程执行阻塞等待 / 父进程执行将当前通道信息(pipefd[1])和对应子进程subid通过ChannelManager,插入到verctor<Channel> _channels 中。
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 subid = fork();if (subid < 0)return false;else if (subid == 0){// 子进程//关闭不需要的写端_cm.CloseAll();// 3. 关闭不需要的文件描述符close(pipefd[1]);Work(pipefd[0]); close(pipefd[0]);exit(0);}else{// 父进程// 3. 关闭不需要的文件描述符close(pipefd[0]); // 写端:pipefd[1];_cm.Insert(pipefd[1], subid);} }return true;}注:上述循环过程结束后,通过_cm.Insert();创建了五个自定义类_channels,这五个_channels中,包含了:
1.各自的管道
2.对应的子进程pid
后续在关闭管道后,可以通过pid来回收子进程(对应管道关闭了,子进程就没有存在的必要了)
2.2.4子进程阻塞等待父进程写入
void Work(int rfd){while (true){int code = 0;ssize_t n = read(rfd, &code, sizeof(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;break;}else{std::cout << "读取错误" << std::endl;break;}}}注:read返回值的含义:
当 n > 0: 成功读取数据,n表示实际读取的字节数
当 n == 0:管道关闭或者没有更多数据可读,表示读到了流的末尾
当 n < 0:读取出错
如果管道内没有数据,read()调用会阻塞,直到有数据可读取。因此不会直接调用n==0的逻辑。
如果管道关闭或没有更多数据,read返回0,进入 n==0 的逻辑,表示子进程退出
2.2.5父进程向管道写入数据
void Run(){// 1. 选择一个任务int taskcode = _tm.Code();//返回一个随机数,随机数不大于总任务个数// 2. 选择一个信道[子进程],负载均衡的选择一个子进程,完成任务auto &c = _cm.Select();// 3. 向管道写入数据/发送任务,然后2.2.4中的子进程会读取到管道中的数据,并开始执行任务c.Send(taskcode);}
2.3管道(Channel)
2.3.1管道大体成员以及函数
class Channel
{
public:Channel(int fd, pid_t id) : _wfd(fd), _subid(id){//实例化对象中包含写入管道的接口以及子进程pid }~Channel(){}void Send(int code){//向管道写入数据int n = write(_wfd, &code, sizeof(code));(void)n; // ?}void Close(){//关闭管道close(_wfd);}void Wait(){//进程等待pid_t rid = waitpid(_subid, nullptr, 0);(void)rid;}
private:int _wfd;pid_t _subid;
};
2.4管道管理(ChannelManager)
2.4.1管道管理大体成员以及函数
class ChannelManager
{
public:ChannelManager() : _next(0){}void Insert(int wfd, pid_t subid){//_channels.emplace_back(wfd, subid);}//管道选取Channel &Select(){auto &c = _channels[_next];_next++;_next %= _channels.size();return c;}//管道关闭//void StopSubProcess()//{// for (auto &channel : _channels)// {// channel.Close();// std::cout << "关闭: " << channel.Name() << std::endl;// }//}//回收子进程//void WaitSubProcess()//{// for (auto &channel : _channels)// {// channel.Wait();// std::cout << "回收: " << channel.Name() << std::endl;// }//}//关闭子进程中多余的写窗口void CloseAll(){for(auto& C : _channel){C.Close();}{//从后往前关void CloseAndWait(){for (int i = _channel.size()-1; i>=0; i--){channel.Close();std::cout << "关闭: " << channel.Name() << std::endl;channel.Wait();std::cout << "回收: " << channel.Name() << std::endl;}}~ChannelManager() {}private:std::vector<Channel> _channels;//通过vector<Channel> 将实例化的管道管理起来int _next;
};
3.命名管道
匿名管道通过父子进程之间的继承关系,能够使得父子进程间看到同一份资源。
问:那么对于两个没有任何血缘关系的进程而言,如何能看到同一份资源?
答:通过命名管道——打开同一个路径下的同一个文件,文件有路径,路径具有唯一性
3.1命名管道的创建
int mkfifo ( const char *filename, mode_t mode);例: mkfifo( "fifo" , 0644 );
3.2匿名管道和命名管道的区别
①:匿名管道由pipe函数创建并打开
②:命名管道由mkfifo创建,并由open打开
注:这样一看,命名管道是不是和文件很相似?
相关文章:
Linux——进程池
前言:大佬写博客给别人看,菜鸟写博客给自己看,我是菜鸟。 1.实现思路 思路:通过创建匿名管道,来实现父子进程之间的通信 注1:父写,子读 注2:匿名管道只能用来进行具有血管关系的进程…...
Qt基于等待条件QWaitCondition实现的任务队列模型示例
核心概念 Qt中的QWaitCondition是一个用于多线程同步的类,允许线程在某些条件满足时唤醒其他等待的线程。它通常与QMutex配合使用,协调线程之间的执行顺序,适用于生产者-消费者模型、任务队列调度等场景。 wait():使当前线程进…...
微服务即时通信系统---(六)语音识别子服务
目录 功能设计 模块划分 业务接口/功能示意图 服务实现流程思想 服务代码实现 编写proto文件 服务端创建子类(SpeechRecognitionServiceImpl)完成RPC服务调用函数重写 SpeechRecognize(语音识别) 服务端完成语音识别子服务类(SpeechRecognitionServer) 注意 …...
JavaWeb基础专项复习5——请求对象和响应对象request and response
系列文章目录 1、JavaWeb基础专项复习1——XML文件-CSDN博客 2、JavaWeb基础专项复习2——JSP文件-CSDN博客 3、JavaWeb基础专项复习2——Servlet相关知识-CSDN博客 4、JavaWeb基础专项复习4——会话对象Session and Cookie-CSDN博客 文章目录 系列文章目录文章目录1、Tom…...
mac下载MAMP6.8.1;解决mac使用小皮面板安装php7.4
因为mac的小皮面板没有php7.4了 链接:c9cc270e6961c17c.dmg官方版下载丨最新版下载丨绿色版下载丨APP下载-123云盘 鹅选一 附上大佬写的教程:MAMP PRO教程 - 牛奔 - 博客园 更新一下,2-27 昨天已经可以使用php7.4了,我就在想能…...
大模型WebUI:Gradio全解12——LangChain原理、架构和组件(3)
大模型WebUI:Gradio全解12——LangChain原理、架构和组件(3) 前言本篇摘要12. LangChain原理及agents构建Gradio UI12.3 LangChain架构12.3.1 LangChain12.3.2 Integration Packages1. 概念2. 示例12.3.3 LangGraph1. 概念2. 示例12.3.4 LangGraph Platform1. 概览2. 优势分…...
redis --- 相关基础知识整理
目录 一、基本1、数据结构2、有序集合的编码1. 压缩列表(Ziplist)2. 跳跃列表(SkipList)3. 动态转换机制 二、应用场景三、持久化1、 RDB 持久化2、 AOF 持久化3、 混合持久化(RDB AOF)4、 RDB和AOF的对比…...
如何用 Python 进行机器学习
文章目录 前言1. 环境准备Python安装选择Python开发环境安装必要库 2. 数据收集与加载3. 数据探索与可视化4. 数据预处理5. 模型选择与训练6. 模型评估7. 模型调优8. 模型部署 前言 使用 Python 进行机器学习一般可以按照以下步骤进行,下面将详细介绍每个步骤及对应…...
《Effective Objective-C》阅读笔记(下)
目录 内存管理 理解引用计数 引用计数工作原理 自动释放池 保留环 以ARC简化引用计数 使用ARC时必须遵循的方法命名规则 变量的内存管理语义 ARC如何清理实例变量 在dealloc方法中只释放引用并解除监听 编写“异常安全代码”时留意内存管理问题 以弱引用避免保留环 …...
解释Promise的工作原理及其状态
Promise的工作原理及其状态 1. 什么是Promise? Promise是JavaScript中的一种用于处理异步操作的对象。它代表一个可能在未来某个时间点完成的操作,并且可以有三种状态:待定(pending)、已解决(fulfilled&a…...
SHELL32!ILCombine函数分析之连接两个idl
SHELL32!ILCombine函数分析之连接两个idl 第一部分: STDAPI_(LPITEMIDLIST) ILCombine(LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2) { // Let me pass in NULL pointers if (!pidl1) { if (!pidl2) { return NULL; …...
es 生产集群的部署架构是什么?每个索引的数据量大概有多少?每个索引大概有多少个分片?
Elasticsearch 生产集群部署架构及面试解析 在后端面试中,Elasticsearch(ES)是一个经常被问到的技术点,尤其是涉及到 生产环境的部署架构。面试官往往希望通过这个问题来验证你是否有真正的生产经验,而不仅仅是玩过一…...
Qt跨线程信号槽调用:为什么信号不能像普通函数那样调用
1. 信号与槽机制的基本原理 在 Qt 中,信号与槽机制是一种事件驱动的通信方式,用于对象之间的解耦交互。其关键特点如下: 信号不能直接调用 信号只是一个声明,并没有实际的函数实现。它们通过 emit 关键字在对象内部被触发&…...
ollama和open-webui部署ds
博客地址: ollama和open-webui部署ds 引言 最近,deepseek是越来越火,我也趁着这个机会做了下私有化部署,我这边使用的ollama和 open-webui实现的web版本 ollama 简介 Ollama 是一个开源的工具,专门用于简化机器学…...
泛微Ecode新增Button调用服务器中的JSP页面里的方法
前言 前端Ecode调用 后端接口编写 JSP文件方法 总结 前言 因为我们是从之前E8版本升级到E9的,所以会有一些接口是通过jsp文件来实现前后端调用的,这里介绍的就是如果你有接口是写在jsp文件里面调用的,但是你又想在Ecode中调用的对应的接…...
LVS+Keepalived高可用群集配置案例
以下是一个 LVSKeepalived 高可用群集配置案例: 1、环境准备 LVS 主调度器(lvs1):IP 地址为 192.168.8.101,心跳 IP 为 192.168.4.101LVS 备调度器(lvs2):IP 地址为 192.168.8.102…...
杰发科技AC7801——滴答定时器获取时间戳
1. 滴答定时器 杰发科技7801内部有一个滴答定时器,该定时器是M0核自带的,因此可以直接用该定时器来获取时间戳。 同样,7803也可以使用该方式获取时间戳。 2. 滴答定时器原理 SysTick是一个24位的递减计数器,它从预设的重装载值…...
Pycharm使用matplotlib出现的问题(1、不能弹出图表 2、图表标题中文不显示)
Pycharm使用matplotlib出现的问题 问题1:Pycharm调试时出现:AttributeError: module backend_interagg has no attribute FigureCanvas. Did you mean: FigureCanvasAgg? 排查原因:可能是由于matplotlib后端设置不正确或与运行环境不兼容引…...
Cursor+pycharm接入Codeuim(免费版),Tab自动补全功能平替
如题,笔者在Cursor中使用pycharm写python程序,试用期到了Tab自动补全功能就不能用了,安装Codeuim插件可以代替这个功能。步骤如下: 1. 在应用商店中搜索扩展Codeuim,下载安装 2. 安装完成后左下角会弹出提示框&#x…...
spring--ApplicationContext和BeanFactory的区别(源码)
ApplicationContext 和 BeanFactory 是 Spring 框架中两个核心的接口,它们都用于管理和访问 Spring 容器中的 Bean,但在功能和使用场景上有显著的区别。以下是它们的详细对比,并结合源码进行讲解。 一、 功能对比 特性BeanFactoryApplicati…...
OpenLayers 可视化之热力图
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 热力图(Heatmap)又叫热点图,是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...
学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2
每日一言 今天的每一份坚持,都是在为未来积攒底气。 案例:OLED显示一个A 这边观察到一个点,怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 : 如果代码里信号切换太快(比如 SDA 刚变,SCL 立刻变&#…...
2025季度云服务器排行榜
在全球云服务器市场,各厂商的排名和地位并非一成不变,而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势,对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析: 一、全球“三巨头”…...
LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》
这段 Python 代码是一个完整的 知识库数据库操作模块,用于对本地知识库系统中的知识库进行增删改查(CRUD)操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 📘 一、整体功能概述 该模块…...
push [特殊字符] present
push 🆚 present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中,push 和 present 是两种不同的视图控制器切换方式,它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...
打手机检测算法AI智能分析网关V4守护公共/工业/医疗等多场景安全应用
一、方案背景 在现代生产与生活场景中,如工厂高危作业区、医院手术室、公共场景等,人员违规打手机的行为潜藏着巨大风险。传统依靠人工巡查的监管方式,存在效率低、覆盖面不足、判断主观性强等问题,难以满足对人员打手机行为精…...
嵌入式常见 CPU 架构
架构类型架构厂商芯片厂商典型芯片特点与应用场景PICRISC (8/16 位)MicrochipMicrochipPIC16F877A、PIC18F4550简化指令集,单周期执行;低功耗、CIP 独立外设;用于家电、小电机控制、安防面板等嵌入式场景8051CISC (8 位)Intel(原始…...
【LeetCode】算法详解#6 ---除自身以外数组的乘积
1.题目介绍 给定一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O…...
