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…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...
【JavaEE】-- HTTP
1. HTTP是什么? HTTP(全称为"超文本传输协议")是一种应用非常广泛的应用层协议,HTTP是基于TCP协议的一种应用层协议。 应用层协议:是计算机网络协议栈中最高层的协议,它定义了运行在不同主机上…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...
AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...
React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...
【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...
短视频矩阵系统文案创作功能开发实践,定制化开发
在短视频行业迅猛发展的当下,企业和个人创作者为了扩大影响力、提升传播效果,纷纷采用短视频矩阵运营策略,同时管理多个平台、多个账号的内容发布。然而,频繁的文案创作需求让运营者疲于应对,如何高效产出高质量文案成…...
搭建DNS域名解析服务器(正向解析资源文件)
正向解析资源文件 1)准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2)服务端安装软件:bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...
