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…...

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...
大学生职业发展与就业创业指导教学评价
这里是引用 作为软工2203/2204班的学生,我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要,而您认真负责的教学态度,让课程的每一部分都充满了实用价值。 尤其让我…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制
在数字化浪潮席卷全球的今天,数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具,在大规模数据获取中发挥着关键作用。然而,传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时,常出现数据质…...

听写流程自动化实践,轻量级教育辅助
随着智能教育工具的发展,越来越多的传统学习方式正在被数字化、自动化所优化。听写作为语文、英语等学科中重要的基础训练形式,也迎来了更高效的解决方案。 这是一款轻量但功能强大的听写辅助工具。它是基于本地词库与可选在线语音引擎构建,…...

视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...

人机融合智能 | “人智交互”跨学科新领域
本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...
【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案
目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后,迭代器会失效,因为顺序迭代器在内存中是连续存储的,元素删除后,后续元素会前移。 但一些场景中,我们又需要在执行删除操作…...
Bean 作用域有哪些?如何答出技术深度?
导语: Spring 面试绕不开 Bean 的作用域问题,这是面试官考察候选人对 Spring 框架理解深度的常见方式。本文将围绕“Spring 中的 Bean 作用域”展开,结合典型面试题及实战场景,帮你厘清重点,打破模板式回答,…...