当前位置: 首页 > news >正文

【Linux】匿名管道通信场景——进程池

🔥 个人主页:大耳朵土土垚
🔥 所属专栏:Linux系统编程

这里将会不定期更新有关Linux的内容,欢迎大家点赞,收藏,评论🥳🥳🎉🎉🎉

文章目录

  • 1. 初始化进程池
  • 2. 进程池执行任务
    • 2.1 任务管理
    • 2.2 执行任务
  • 3. 清理进程池
  • 4. 封装与完整实现
  • 5. 结语

1. 初始化进程池

  进程池的实现是依靠匿名管道,通过进程间通信使得父进程能够管理多个进程任务,相当于父进程拥有了很多个进程——进程池,通过不同的进程完成指定的任务。
  所以我们需要创建多个匿名管道和子进程,进行进程间通信,发送信息给子进程让它们根据接收到的信息处理相关任务。
  因为有多个管道和子进程,为了方便父进程使用不同管道发送对应信息给子进程,我们需要将管道的文件描述符以及对应子进程的pid保存起来,我们选择将它们封装在一个Channel类中。又因为有多个匿名管道和子进程,所以将多个Channel类对象储存在C++STL中的容器vector中来方便父进程进行管理进程池。

代码如下:

int InitProcesspool(int num,std::vector<Channel>& channels)
{for(int i = 0; i < num; i++)//使用循环创建多个匿名管道和子进程{//1.创建匿名管道int pipefd[2] = {0};int n = pipe(pipefd);if(n < 0) return 2;//根据不同的返回值判断原因,也可以使用枚举来约定返回值代表的内容//2.创建子进程pid_t id = fork();if(id < 0) return 3;//3.建立通信管道,父子进程关闭读端或写端if(id == 0)//子进程{//子进程读取,关闭写端::close(pipefd[1]);//dup2dup2(pipefd[0],0);//子进程需要执行的内容Work();::exit(0);}//父进程//父进程写入,关闭读端::close(pipefd[0]);channels.emplace_back(pipefd[1],id);//保存在channel对象中并存入vector}return 0;
}

对子进程内部,我们使用dup2系统调用将匿名管道读端文件描述符与标准输入stdin交换,这样我们就不需要保存不同进程对应匿名管道的读端文件描述符,只需要统一从0号文件描述符中读取内容即可。

对于Channel类

class Channel{
public:Channel(int fd,pid_t who):_fd(fd),_who(who){_name = "Channel-"+std::to_string(fd)+"-"+std::to_string(who);}std::string GetName(){return _name;}int GetFd(){return _fd;}pid_t GetWho(){return _who;}void Send(int num)//父进程往匿名管道发送信息{::write(_fd,&num,sizeof(num));}~Channel(){}
private:int _fd;//保存匿名管道通信的文件描述符std::string _name;//名字(自己取的)pid_t _who;//子进程pid
};

对于父进程发送给子进程的信息我们选择约定一个数字对应一个任务,不同数字对应不同需要完成的任务,子进程接收到信息后就可以根据数字来确定不同的任务。

2. 进程池执行任务

2.1 任务管理

  执行任务之前我们需要先确定有哪些任务,怎么执行…所以我们需要进行任务管理,同样我们也是使用一个类TaskManager来进行任务管理:

#include<iostream>
#include<unordered_map>
#include<functional>
#include<ctime>using task_t = std::function<void()>;//函数指针//不同任务函数void Load(){std::cout<<"正在执行加载任务..."<<std::endl;}void Del(){std::cout<<"正在执行删除任务..."<<std::endl;}void Log(){std::cout<<"正在执行日志任务..."<<std::endl;}static int number = 0;
class TaskManager
{
public:TaskManager(){srand(time(nullptr));InsertTask(Load);InsertTask(Del);InsertTask(Log);}int SelectTask(){return rand()%number;}void InsertTask(task_t t){m[number++] = t;}void Excute(int num){if(m.find(num) == m.end())return;m[num]();//执行任务}~TaskManager(){}
private:std::unordered_map<int,task_t> m;//使用map封装数字与对应的任务函数指针
};TaskManager tm;

  选择新创建一个源文件Task.hpp来封装上述内容,上述任务管理类中我们使用map来保存数字与任务函数指针的相关关系,这样通过数字就可以确定是哪一个任务函数;此外选择任务使用的方法是随机数的方法,大家可以根据自己的想法确定不同的方式。

2.2 执行任务

  • 发送任务

使用按顺序轮询的方式派发任务给不同的子进程——设置10次任务循环,先通过任务管理类中的选择函数获取任务编号,然后通过父进程进程池管理类将任务编号发送给子进程。

void ExcuteTask(std::vector<Channel>& channels)
{int n = 0;int count = 10;while(count--)//执行10次任务{//1.选择任务,获取任务编号int tasknum = tm.SelectTask();//2.选择子进程,使用轮询选择,派发任务channels[n++].Send(tasknum);n%=channels.size();std::cout<<std::endl;std::cout<<"*****成功发送"<<10-count<<"个任务*****"<<std::endl;sleep(2);//每个2s发送一个任务}
}
  • 接受并执行任务

子进程接受并执行任务——先通过匿名管道接受父进程发送的任务编号,然后通过任务管理类对象执行任务编号所对应的任务函数。

//子进程接受并执行任务
void Work()
{while(true){int num = 0;int n = ::read(0,&num,sizeof(num));if(n == sizeof(num))//读取成功tm.Excute(num);//不要发成nelse if(n == 0){break;}else{break;}}
}

3. 清理进程池

我们需要回收匿名管道的文件描述符和子进程

void CleanProcesspool(std::vector<Channel>& channels)
{for(auto& c : channels)::close(c.GetFd());for(auto& c : channels){pid_t rid = ::waitpid(c.GetWho(),nullptr,0);if(rid <= 0){std::cout<<std::endl;std::cout<<"清理子进程失败..."<<std::endl;return;}}std::cout<<std::endl;std::cout<<"成功清理子进程..."<<std::endl;
}

  注意这里不能使用一个循环来进行清理,如下面代码是错误的:

void CleanProcesspool(std::vector<Channel>& channels)
{for(auto& c : channels)//只使用一次循环{::close(c.GetFd());pid_t rid = ::waitpid(c.GetWho(),nullptr,0);if(rid <= 0){std::cout<<std::endl;std::cout<<"清理子进程失败..."<<std::endl;return;}}std::cout<<std::endl;std::cout<<"成功清理子进程..."<<std::endl;
}

这是因为在创建子进程时,子进程会继承父进程的文件描述符表,因此在第一个匿名管道创建后,例如父进程的4号文件描述符指向该匿名管道写端,那么在创建第二个子进程时,该子进程会继承4号文件描述符也指向第一个匿名管道写端,因此创建的子进程越多,前面匿名管道写端被指向的就越多,所以仅仅关闭一个进程的写端指向,还有其他的写端指向,所以读端无法读到0,也就无法退出,如下图所示:
在这里插入图片描述
在这里插入图片描述

当创建2个子进程时,第一个匿名管道写端就有两个进程指向,当创建的进程越多,该写端指向的也就越多。

如果要使用一个循环来清理回收子进程,我们可以从后往前关闭文件描述符,因为最后一个管道写端只有父进程指向。

4. 封装与完整实现

  对于父进程管理进程池我们可以封装一个类来更好的进行管理与实现:

#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>#include "Task.hpp"
#include "Channel.hpp"
// masterclass ProcessPool
{
public:int InitProcesspool(int num){for (int i = 0; i < num; i++){// 1.创建匿名管道int pipefd[2] = {0};int n = pipe(pipefd);if (n < 0)return 2;// 2.创建子进程pid_t id = fork();if (id < 0)return 3;// 3.建立通信管道,父子进程关闭读端或写端if (id == 0) // 子进程{// 子进程读取,关闭写端::close(pipefd[1]);// dup2dup2(pipefd[0], 0);Work();::exit(0);}// 父进程// 父进程写入,关闭读端::close(pipefd[0]);channels.emplace_back(pipefd[1], id);}return 0;}void ExcuteTask(){int n = 0;int count = 10;while (count--) // 执行10次任务{// 1.选择任务,获取任务编号int tasknum = tm.SelectTask();// 2.选择子进程,使用轮询选择,派发任务channels[n++].Send(tasknum);n %= channels.size();std::cout << std::endl;// std::cout<<"**************************"<<std::endl;std::cout << "*****成功发送" << 10 - count << "个任务*****" << std::endl;// std::cout<<"**************************"<<std::endl;// std::cout<<std::endl;sleep(3);}}void CleanProcesspool(){for (auto &c : channels)::close(c.GetFd());for (auto &c : channels){pid_t rid = ::waitpid(c.GetWho(), nullptr, 0);if (rid <= 0){std::cout << std::endl;std::cout << "清理子进程失败..." << std::endl;return;}}std::cout << std::endl;std::cout << "成功清理子进程..." << std::endl;}private:std::vector<Channel> channels;
};

main函数:

#include "ProcessPool.hpp"int main(int argc, char* argv[])
{//0.获取应该创建管道个数num个if(argc!=2){std::cout<<"请输入管道个数."<<std::endl;return 1;}int num = std::stoi(argv[1]);std::vector<Channel> channels;ProcessPool* pp = new ProcessPool;//1.初始化进程池——创建进程池pp->InitProcesspool(num);//2.执行任务pp->ExcuteTask();//3.任务执行完成,回收子进程pp->CleanProcesspool();delete pp;return 0;
}

运行结果如下:

在这里插入图片描述

5. 结语

  以上就是基于匿名管道通信实现的进程池,子进程会根据接受到的信息执行不同的任务,父进程可以看作Master来进行管理创建的多个进程。关键点在于对进程管理的封装以及回收子进程时会有多个进程指向匿名管道的读端,所以回收时要注意可能会出现的bug。

相关文章:

【Linux】匿名管道通信场景——进程池

&#x1f525; 个人主页&#xff1a;大耳朵土土垚 &#x1f525; 所属专栏&#xff1a;Linux系统编程 这里将会不定期更新有关Linux的内容&#xff0c;欢迎大家点赞&#xff0c;收藏&#xff0c;评论&#x1f973;&#x1f973;&#x1f389;&#x1f389;&#x1f389; 文章目…...

算法妙妙屋-------1.递归的深邃回响:全排列的奇妙组合

全排列的简要总结 全排列&#xff08;Permutation&#xff09;是数学中一个经典的问题&#xff0c;指的是从一组元素中&#xff0c;将所有元素按任意顺序排列形成的所有可能序列。 特点 输入条件&#xff1a; 给定一组互异的元素&#xff08;通常为数组或字符串&#xff09;。…...

【maven-6】Maven 生命周期相关命令演示

Maven 是一个广泛使用的项目管理工具&#xff0c;尤其在 Java 项目中。它通过定义一系列的生命周期阶段&#xff08;Phases&#xff09;来管理项目的构建过程。理解这些生命周期阶段及其相关命令&#xff0c;对于高效地构建和管理项目至关重要。本文将通过实际演示&#xff0c;…...

黑马程序员Java笔记整理(day06)

1.继承的特点 2.继承的权限 3. 4.小结 5.方法重写 6.子类构造器 7.兄弟构造器 8.多态 9.小结...

LeetCode【代码随想录】刷题(动态规划篇)

509. 斐波那契数 力扣题目链接 题目&#xff1a;斐波那契数&#xff08;通常用F(n)表示&#xff09;形成的序列称为斐波那契数列 。该数列由0和1开始&#xff0c;后面的每一项数字都是前面两项数字的和。也就是&#xff1a; F(0) 0&#xff0c;F(1) 1 F(n) F(n - 1) F(n…...

【看海的算法日记✨优选篇✨】第三回:二分之妙,寻径中道

&#x1f3ac; 个人主页&#xff1a;谁在夜里看海. &#x1f4d6; 个人专栏&#xff1a;《C系列》《Linux系列》《算法系列》 ⛰️ 一念既出&#xff0c;万山无阻 目录 &#x1f4d6;一、算法思想 细节问题 &#x1f4da;左右临界 &#x1f4da;中点选择 &#x1f4da;…...

基于yolov8、yolov5的铝材缺陷检测识别系统(含UI界面、训练好的模型、Python代码、数据集)

摘要&#xff1a;铝材缺陷检测在现代工业生产和质量管理中具有重要意义&#xff0c;不仅能帮助企业实时监控铝材质量&#xff0c;还为智能化生产系统提供了可靠的数据支撑。本文介绍了一款基于YOLOv8、YOLOv5等深度学习框架的铝材缺陷检测模型&#xff0c;该模型使用了大量包含…...

计算机光电成像理论基础

一、透过散射介质成像 1.1 光在散射介质中传输 光子携带物体信息并进行成像的过程是一个涉及光与物质相互作用的物理现象。这个过程可以分为几个步骤来理解&#xff1a; 1. **光的发射或反射**&#xff1a; - 自然界中的物体可以发射光&#xff08;如太阳&#xff09;&am…...

【QNX+Android虚拟化方案】125 - 如何创建android-spare镜像

【QNX+Android虚拟化方案】125 - 如何创建android-spare镜像 1. Android侧创建 (ext4 / sparse) test_img.img 镜像 方法一2. Android侧创建 (ext4 / sparse) test_img.img 镜像 方法二3. qnx 侧 分区透传Android 配置3.1 配置分区透传3.2 Android 侧分区 rename3.3 创建挂载目…...

深度学习基础小结_项目实战:手机价格预测

目录 库函数导入 一、构建数据集 二、构建分类网络模型 三、编写训练函数 四、编写评估函数 五、网络性能调优 鲍勃开了自己的手机公司。他想与苹果、三星等大公司展开硬仗。 他不知道如何估算自己公司生产的手机的价格。在这个竞争激烈的手机市场&#xff0c;你不能简单地…...

EMall实践DDD模拟电商系统总结

目录 一、事件风暴 二、系统用例 三、领域上下文 四、架构设计 &#xff08;一&#xff09;六边形架构 &#xff08;二&#xff09;系统分层 五、系统实现 &#xff08;一&#xff09;项目结构 &#xff08;二&#xff09;提交订单功能实现 &#xff08;三&#xff0…...

【随笔】AI技术在电商中的应用

这几年&#xff0c;伴随着ChatGPT开始的AI浪潮席卷全球&#xff0c;从聊天场景逐步向多场景扩散&#xff0c;形成了广泛开花的现象。至今&#xff0c;虽然在部分场景的进展已经略显疲态&#xff0c;但当前的这种趋势仍然还在不断的扩展。不少公司&#xff0c;甚至有不少大型电商…...

序列式容器详细攻略(vector、list)C++

vector std::vector 是 STL 提供的 内存连续的、可变长度 的数组(亦称列表)数据结构。能够提供线性复杂度的插入和删除,以及常数复杂度的随机访问。 为什么要使用 vector 作为 OIer,对程序效率的追求远比对工程级别的稳定性要高得多,而 vector 由于其对内存的动态处理,…...

快速启动项目

1 后端项目 https://gitee.com/liuyunkai666/gungun-boot.git 分支&#xff1a; mini 是 springboot3 jdk17 的基础版本&#xff0c;后续其他功能模块陆续在其基础上追加即可​。 1.1 必备环境 1.1.1 mysql 创建一个 自定义名称 数据库&#xff0c;【只要】 执行对应数据库…...

springboot347基于web的铁路订票管理系统(论文+源码)_kaic

摘 要 当今社会进入了科技进步、经济社会快速发展的新时代。计算机技术对经济社会发展和人民生活改善的影响也日益突出&#xff0c;人类的生存和思考方式也产生了变化。传统铁路订票管理采取了人工的管理方法&#xff0c;但这种管理方法存在着许多弊端&#xff0c;比如效率低…...

使用API管理Dynadot域名,在账户中添加域名服务器(Name Server)

前言 Dynadot是通过ICANN认证的域名注册商&#xff0c;自2002年成立以来&#xff0c;服务于全球108个国家和地区的客户&#xff0c;为数以万计的客户提供简洁&#xff0c;优惠&#xff0c;安全的域名注册以及管理服务。 Dynadot平台操作教程索引&#xff08;包括域名邮箱&…...

【Linux | 计网】TCP协议深度解析:从连接管理到流量控制与滑动窗口

目录 前言&#xff1a; 1、三次握手和四次挥手的联系&#xff1a; 为什么挥手必须要将ACK和FIN分开呢&#xff1f; 2.理解 CLOSE_WAIT 状态 CLOSE_WAIT状态的特点 3.FIN_WAIT状态讲解 3.1、FIN_WAIT_1状态 3.2、FIN_WAIT_2状态 3.3、FIN_WAIT状态的作用与意义 4.理解…...

go语言的成神之路-筑基篇-对文件的操作

目录 一、对文件的读写 Reader 接口 Writer接口 copy接口 bufio的使用 ioutil库 二、cat命令 三、包 1. 包的声明 2. 导入包 3. 包的可见性 4. 包的初始化 5. 标准库包 6. 第三方包 7. 包的组织 8. 包的别名 9. 包的路径 10. 包的版本管理 四、go mod 1. 初始…...

两道数据结构编程题

1.写出在顺序存储结构下将线性表逆转的算法&#xff0c;要求使用最少的附加空间。 解&#xff1a;输入&#xff1a;长度为n的线性表数组A(1:n) 输出&#xff1a;逆转后的长度为n的线性表数组A(1:n)。 C语言描述如下&#xff08;其中ET为数据元素的类型&#xff09;&#xff1a;…...

【Qt】QDateTimeEdit控件实现清空(不保留默认时间/最小时间)

一、QDateTimeEdit控件 QDateTimeEdit 提供了一个用于编辑日期和时间的控件。用户可以通过键盘或使用上下箭头键来增加或减少日期和时间值。日期和时间的显示格式根据设置的格式显示&#xff0c;可以通过 setDisplayFormat() 方法来设置。 二、如何清空 我在使用的时候&#…...

C/C++进阶知识1.0

C/C进阶知识 1.delete与delete[ ] ClassA *pclassanew ClassA[5]; delete pclassa; 与 int *p new int[5]; delete p; 1.1内置类型 不调用析构函数 1.2自定义类型 析构函数调用一次 2.内存知识 2.1栈堆增长方向不同的原因&#xff1a; 栈向下增长堆向上增长的设计目的是…...

用74ls10和74ls20与非门搭建四人表决器:从真值表到电路图的完整设计流程

用74LS10和74LS20与非门搭建四人表决器&#xff1a;从真值表到电路图的完整设计流程 在数字电路设计中&#xff0c;表决器是一个经典的教学案例&#xff0c;它不仅能帮助理解组合逻辑电路的基本原理&#xff0c;还能锻炼从理论到实践的完整设计能力。本文将手把手带你用74LS10…...

Revolut警告支持高耗能AI和加密货币业务可能面临声誉风险

英国银行应用Revolut表示&#xff0c;由于支持加密货币和AI等高耗能行业&#xff0c;公司可能面临声誉风险&#xff0c;同时该公司公布去年利润增长57%。这家金融科技公司在等待监管批准五年后&#xff0c;现在终于可以作为正式的英国银行启动业务。Revolut在其2025年年报中警告…...

FBGA200封装揭秘:为什么长鑫这款LPDDR4X内存更适合工业级嵌入式设备?

FBGA200封装工业级LPDDR4X内存的五大实战优势 在工业自动化生产线控制柜里&#xff0c;一块仅有指甲盖大小的内存模块正在零下20度的环境中稳定处理着每秒上千条传感器数据&#xff1b;与此同时&#xff0c;行驶在戈壁滩的智能矿卡车载系统中&#xff0c;同款内存芯片正承受着持…...

Java 新纪元 — JDK 25 + Spring Boot 4 全栈实战(十七):Boot 3 → Boot 4 迁移避坑指南——那些文档不会告诉你的迁移血泪史

系列导航 | ← 上一篇:D16 Spring Boot 4 + AI推理后端集成 | 下一篇:D18 云原生部署:Docker + K8s + GraalVM → 适用读者:正在从 Spring Boot 3.x 升级到 4.x 的开发者,或在评估升级可行性的架构师。 前置知识:熟悉 Spring Boot 3.x 开发,了解 JDK 21+ 基本特性。 本文…...

2026年高性价比个人网盘盘点:告别“空间焦虑”,谁才是真正的效率神器?

在预算有限的情况下寻找个人网盘&#xff0c;大多数人的第一反应是打开计算器&#xff0c;算出“每 GB 只要几分钱”。但作为一个在存储行业摸爬滚打多年的老兵&#xff0c;我要告诉你一个反直觉的事实&#xff1a;对于 90% 的办公族和学生来说&#xff0c;网盘的“空间大小”是…...

YOLOv8 Detect Head 源码拆解:从张量变形到边界框解码,一步步带你理解Anchor-Free预测

YOLOv8 Detect Head 深度解析&#xff1a;从特征图到预测框的完整实现路径 在计算机视觉领域&#xff0c;目标检测一直是核心任务之一。YOLOv8作为当前最先进的实时检测器&#xff0c;其Detect Head模块的设计尤为精妙。本文将带您深入探索这一模块的内部工作机制&#xff0c;从…...

简单三步上手:bilibili-parse视频解析工具完整指南

简单三步上手&#xff1a;bilibili-parse视频解析工具完整指南 【免费下载链接】bilibili-parse bilibili Video API 项目地址: https://gitcode.com/gh_mirrors/bi/bilibili-parse 还在为无法离线观看B站视频而烦恼吗&#xff1f;bilibili-parse是一个强大的B站视频解析…...

OpenClaw学术研究助手:Qwen3-32B驱动的论文摘要与笔记整理

OpenClaw学术研究助手&#xff1a;Qwen3-32B驱动的论文摘要与笔记整理 1. 为什么需要本地化的AI研究助手&#xff1f; 去年冬天&#xff0c;我在整理一个跨学科研究项目的文献时陷入了困境。手头堆积了200多篇PDF论文&#xff0c;每篇都需要提取核心观点、记录关键数据&#…...

java毕业设计基于SpringBoot酒店预定系统

前言 Spring Boot酒店预定系统是一种功能丰富、易于维护和扩展的在线预订平台。它通过整合前后端技术&#xff0c;实现了酒店信息的在线展示、预订、支付以及管理等一系列功能&#xff0c;为用户和酒店提供了便捷、高效的预订服务。随着旅游业和酒店业的不断发展&#xff0c;该…...