【Linux】匿名管道的应用场景-----管道进程池
目录
一、池化技术
二、简易进程池的实现:
Makefile
task.h
task.cpp
Initchannel函数:
创建任务:
控制子进程:
子进程执行任务:
清理收尾:
三、全部代码:
前言:
对于管道,我们已经学习了匿名管道,那么这个匿名管道有什么用呢?接下来,我们以池化技术来实现一个简易的管道进程池
一、池化技术
1、内存池:减少内存分配的系统调用开销
问题:
当使用new或 malloc 动态分配内存时,每次都会触发系统调用
例如:每次申请5字节,或者申请10字节,再次申请20字节,会触发三次系统调用
系统调用成本高,因为操作系统可能正在处理其他任务,导致等待,降低效率
解决方案:
一次性向操作系统申请更大的内存空间(如100字节或200字节)
后续需要内存时,直接从这块预先申请好的空间中分配,避免频繁触发系统调用
优点:
减少系统调用次数,分摊开销,提高内存分配效率
2、进程池:减少进程创建的开销
问题:
传统方式是父进程创建子进程,子进程完成任务后退出,父进程等待
每次创建子进程时,操作系统需要复制task_struct,页表等数据结构,开销较大
频繁创建和销毁进程效率低下
解决方案:
预先创建一批子进程,作为资源储备(进程池)
当有任务到来时,父进程直接将任务分配给池中已有的子进程,而不是每次都创建新进程
优点:
减少进程创建的开销,提高任务分配和执行的效率
3. 核心思想
资源预分配
无论是内存池还是进程池,核心思想都是预先分配资源,避免重复的系统调用或资源创建
提高效率:
通过减少高频操作的开销,显著提升程序性能,尤其适用于高并发或高性能场景
总结
内存池:一次性申请大块内存,后续直接从池中分配,减少系统调用
进程池:预先创建一批进程,任务到来时直接分配,减少进程创建开销
共同目标:通过资源预分配,优化性能,降低系统开销
二、简易进程池的实现:
在了解上述的池化技术后,我们以这种思想来设计一个简易的进程池

如上,这是一个简易的进程池,我们让父进程向子进程发送数据也就是父进程向管道中写入数据,然后子进程从管道中读数据,这样父进程创建多个子进程,每次选择某个子进程和管道进行数据的传输,这样就是一个简易的进程池了
Makefile
task:tesk.cppg++ -o $@ $^ -g -std=c++11
.PHONY:clean
clean:rm -f task
task.h
#pragma once #include<iostream>
#include<vector>
#include<string>
task.cpp
先描述:
首先通过class类来描述一个管道
#include"task.h"//先描述,描述管道
class channel
{
public://构造函数通过列表进行初始化channel(int fd, int childpid, const std::string &processname):_fd(fd),_childpid(childpid),_processname(processname){}
public:int _fd; //管道写端的文件描述符pid_t _childpid; //管道所连接表示子进程的pidstd::string _processname; //管道所连接的子进程的名字
};int main()
{//加载好任务//通过vector将一个个管道组织起来std::vector<channel> channels;//初始化,创建管道Initchannel(&channels);//控制子进程ctrlchild(channels);//清理收尾quitprocess(channels);return 0;
}
在组织:
在通过vector这个容器来将一个个管道组织起来,然后进行初始化,控制子进程,清理等操作
Initchannel函数:
创建管道,这个管道是连接父进程和子进程的,所以我们要创建几个管道就需要创建几个子进程
通过pipe创建管道文件

初始化:
void Initchannel(std::vector<channel>* channels)
{for(int i = 0;i<processnum;i++){//创建管道int pipefd[2];int n = pipe(pipefd);//创建管道文件if(n != 0) exit(0);//创建失败就退出程序//创建完管道文件然后创建管道文件对应的子进程pid_t id = fork();if(id == 0){//子进程模块,这里子进程去读,所以关闭写通道close(pipefd[1]);//重定向dup2(pipefd[0],0);work();//子进程进行工作的函数模块exit(0);//工作完就结束子进程}//父进程去写所以这里关闭读端close(pipefd[0]);std::string name = "process"+std::to_string(i);channels->push_back(channel(pipefd[1],id,name));}
}
创建任务:
//重定义函数指针
typedef void(* task)();void task1()
{std::cout<<"刷新野区"<<std::endl;
}void task2()
{std::cout<<"刷新技能"<<std::endl;
}void task3()
{std::cout<<"泉水回血"<<std::endl;
}void task4()
{std::cout<<"技能耗蓝"<<std::endl;
}void Loadtask(std::vector<task>* tasks)
{tasks->push_back(task1);tasks->push_back(task2);tasks->push_back(task3);tasks->push_back(task4);
}
其中Loadtask函数的作用就是保存所执行的任务
接着在程序最开始(也就是在main函数的最开始),将我们的任务都进行加载好
还需要全局变量
processnum:
控制进程池的大小,决定创建多少个子进程。
方便扩展,例如通过配置文件动态设置子进程数量。
tasks:
集中管理所有任务,便于父进程和子进程共享任务列表。
通过任务码(索引)快速定位和执行任务。
const int processnum = 5;//进程中子进程的数量
std::vector<task> tasks;//存储任务的容器int main()
{//加载好任务Loadtask(&tasks);//通过vector将一个个管道组织起来std::vector<channel> channels;//初始化,创建管道Initchannel(&channels);//控制子进程ctrlchild();//清理收尾quitprocess();return 0;
}
手动控制子进程
这里首先搞一个菜单出来
void menu()
{std::cout << "#########################################" << std::endl;std::cout << "# 1、刷新野区 2、刷新技能 #" << std::endl;std::cout << "# 3、泉水回血 4、技能耗蓝 #" << std::endl;std::cout << "# 0、退出 #" << std::endl;std::cout << "#########################################" << std::endl;
}
在控制子进程的过程中,我们首先选好我们要完成的任务,然后向管道里写一个任务码,然后被选中的子进程就会从管道中找到任务码,就可以根据vector<task>里面的任务知道需要执行哪一个任务了
控制子进程:
根据菜单,每次输入任务码的时候,就通过write系统调用来确定子进程,发送任务
void ctrlchild(std::vector<channel>& channels)
{int which = 0;while(1){menu();int selet = 0;std::cout<<"请输入所选择的任务";std::cin>>selet;//判断所选的任务码是否合法if(selet<=0 || selet>=5) break;//任务选择int taskcode = selet - 1;//子进程选择,轮询法std::cout<<"father say taskcode :" << taskcode <<" already send to " << channels[which]._childpid << "process name " << channels[which]._processname << std::endl;//发送任务write(channels[which]._fd,&taskcode,sizeof(taskcode));//确定子进程,子进程所接收的任务码,和所接受的大小//保证所选的进程合法which++;which %= channels.size();}
}
子进程执行任务:
这是子进程的核心代码,通过read系统调用接口从管道中读到任务码,在通过这个任务码和函数指针来调用任务
//子进程工作代码,通过父进程向管道发送的任务码找到对应的任务,然后执行
void work()
{while(1){int teskcode = 0;int n = read(0, &teskcode, sizeof(teskcode)); //read返回的是读取到的字节的个数。 //然后第二个参数是要读到哪里去,第三个参数是读取的大小,这一行代码执行完后,teskcode里面保存的就是要执行的任务码if (n == sizeof(teskcode)){std::cout << "work get a command : " << getpid() << " : " << "teskcode" << teskcode << std::endl;if (teskcode >= 0 && teskcode < tasks.size()) tasks[teskcode]();}if (n == 0) break; //如果读到0, 说明写端关闭, 读端读到文件末尾, 就需要停止读取了}
}
清理收尾:
在清理收尾的时候不能够直接关闭然后父进程等待子进程,这样是有问题的,因为当父进程创建子进程的时候,子进程的读端也会指向对应的管道,这样的话一个管道就会有很多个读端了,如下:

每一个子进程的写端都会指向之前的所有管道,比如,如果上述有3个进程的话,那么第一个管道就有3个写端(父进程一个,父进程创建的两个子进程的写端都会指向第一个管道),那么如果直接close(c._fd)关闭管道的写端的话,那么是不能够关闭完全的,需要全部关闭,这里有两种解决方式:
第一种方式:
从后往前进行关闭,下面代码已给出,我们知道最后一个管道依然是只有一个写端的,那么当最后一个管道关闭后,前面的管道的写端就都会少一个,这样的话从后往前关闭就不会出现这样的问题了
void quitprocess(const std::vector<channel> &channels)
{//出现问题for(const auto &c : channels){close(c._fd); waitpid(c._childpid,nullptr,0);}// //解决方案1// int last = channels.size()-1;// for(int i = last;i>=0;i--)// {// close(channels[i]._fd);// waitpid(channels[i]._childpid,nullptr,0);// }// for(const auto &c : channels)// {// close(c._fd);// }// for(const auto &c : channels)// {// waitpid(c._childpid,nullptr,0);// }
}
第二种方式:
在创建管道的时候进行记录父进程所占用的文件描述符

三、全部代码:
makefile
task:task.cppg++ -o $@ $^ -g -std=c++11
.PHONY:clean
clean:rm -f task
task.h
#pragma once #include<iostream>
#include<vector>
#include<string>#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>//重定义函数指针
typedef void(* task)();void task1()
{std::cout<<"刷新野区"<<std::endl;
}void task2()
{std::cout<<"刷新技能"<<std::endl;
}void task3()
{std::cout<<"泉水回血"<<std::endl;
}void task4()
{std::cout<<"技能耗蓝"<<std::endl;
}//保存所执行的任务
void Loadtask(std::vector<task>* tasks)
{tasks->push_back(task1);tasks->push_back(task2);tasks->push_back(task3);tasks->push_back(task4);
}
task.cpp
#include"task.h"const int processnum = 5;
std::vector<task> tasks;
//先描述,描述管道
class channel
{
public://构造函数通过列表进行初始化channel(int fd, int childpid, const std::string &processname):_fd(fd),_childpid(childpid),_processname(processname){}
public:int _fd; //表示父进程链接某个管道的fdpid_t _childpid; //管道所连接表示子进程的pidstd::string _processname; //管道所连接的子进程的名字
};//子进程工作代码,通过父进程向管道发送的任务码找到对应的任务,然后执行
void work()
{while(1){int teskcode = 0;int n = read(0, &teskcode, sizeof(teskcode)); //read返回的是读取到的字节的个数。 //然后第二个参数是要读到哪里去,第三个参数是读取的大小,这一行代码执行完后,teskcode里面保存的就是要执行的任务码if (n == sizeof(teskcode)){std::cout << "work get a command : " << getpid() << " : " << "teskcode" << teskcode << std::endl;if (teskcode >= 0 && teskcode < tasks.size()) tasks[teskcode]();}if (n == 0) break; //如果读到0, 说明写端关闭, 读端读到文件末尾, 就需要停止读取了}
}void Initchannel(std::vector<channel>* channels)
{//解决方案2std::vector<int> oldfd;//创建一个数组记录父进程所在的文件描述符的写端for(int i = 0;i<processnum;i++){//创建管道int pipefd[2];int n = pipe(pipefd);//创建管道文件if(n != 0) exit(0);//创建失败就退出程序//创建完管道文件然后创建管道文件对应的子进程pid_t id = fork();if(id == 0){//将父进程所在的文件描述符写端,子进程将其关闭for(auto fd : oldfd) close(fd);//子进程模块,这里子进程去读,所以关闭写通道close(pipefd[1]);//重定向dup2(pipefd[0],0);work();//子进程进行工作的函数模块std::cout << "Process PID: " << getpid() << " quit" << std::endl;exit(0);//工作完就结束子进程}//父进程去写所以这里关闭读端close(pipefd[0]);std::string name = "process"+std::to_string(i);channels->push_back(channel(pipefd[1],id,name));//每次记录父进程写端所在的文件描述符oldfd.push_back(pipefd[1]); }
}void menu()
{std::cout << "#########################################" << std::endl;std::cout << "# 1、刷新野区 2、刷新技能 #" << std::endl;std::cout << "# 3、泉水回血 4、技能耗蓝 #" << std::endl;std::cout << "# 0、退出 #" << std::endl;std::cout << "#########################################" << std::endl;
}void ctrlchild(std::vector<channel>& channels)
{int which = 0;while(1){menu();int selet = 0;std::cout<<"请输入所选择的任务";std::cin>>selet;//判断所选的任务码是否合法if(selet<=0 || selet>=5) break;//任务选择int taskcode = selet - 1;//子进程选择,轮询法std::cout<<"father say taskcode :" << taskcode <<" already send to " << channels[which]._childpid << "process name " << channels[which]._processname << std::endl;//发送任务write(channels[which]._fd,&taskcode,sizeof(taskcode));//确定子进程,子进程所接收的任务码,和所接受的大小//保证所选的进程合法which++;which %= channels.size();}
}void quitprocess(const std::vector<channel> &channels)
{//出现问题for(const auto &c : channels){close(c._fd); waitpid(c._childpid,nullptr,0);}// //解决方案1// int last = channels.size()-1;// for(int i = last;i>=0;i--)// {// close(channels[i]._fd);// waitpid(channels[i]._childpid,nullptr,0);// }// for(const auto &c : channels)// {// close(c._fd);// }// for(const auto &c : channels)// {// waitpid(c._childpid,nullptr,0);// }
}int main()
{//加载好任务Loadtask(&tasks);//通过vector将一个个管道组织起来std::vector<channel> channels;//初始化,创建管道Initchannel(&channels);//控制子进程ctrlchild(channels);//清理收尾quitprocess(channels);return 0;
}相关文章:
【Linux】匿名管道的应用场景-----管道进程池
目录 一、池化技术 二、简易进程池的实现: Makefile task.h task.cpp Initchannel函数: 创建任务: 控制子进程: 子进程执行任务: 清理收尾: 三、全部代码: 前言: 对于管…...
机器学习(1)安装Pytorch
1.安装命令 pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 2.安装过程Log: Looking in indexes: https://download.pytorch.org/whl/cu118 Co…...
Linux 多Python版本统一和 PySpark 依赖 python 包方案
背景 Linux 服务器经常有多个Python版本,比如 Python2 有两个版本,Python3 有两个版本。在使用上容易混淆,而且有些需要新增一些 module 更容易,安装如果路径不统一,导致日常使用时,会出现找不到新安装mod…...
PostgreSQL的学习心得和知识总结(一百六十九)|深入理解PostgreSQL数据库之 Group By 键值消除 的使用和实现
目录结构 注:提前言明 本文借鉴了以下博主、书籍或网站的内容,其列表如下: 1、参考书籍:《PostgreSQL数据库内核分析》 2、参考书籍:《数据库事务处理的艺术:事务管理与并发控制》 3、PostgreSQL数据库仓库…...
DeepSeek是什么?两种模型的对比?
最近DeepSeek的风也是很大,它也是很火,那么DeepSeek是什么呢? 什么是DeepSeek? DeepSeek是一家专注通用人工智能(AGI)的中国科技公司,主攻大模型研发与应用。DeepSeek-R1是其开源的推理模型&a…...
跟着 Lua 5.1 官方参考文档学习 Lua (2)
文章目录 2.3 – Variables2.4 – Statements2.4.1 – Chunks2.4.2 – Blocks2.4.3 – Assignment2.4.4 – Control Structures2.4.5 – For Statement2.4.6 – Function Calls as Statements2.4.7 – Local Declarations 2.3 – Variables Variables are places that store v…...
Python基于循环神经网络的情感分类系统(附源码,文档说明)
博主介绍:✌IT徐师兄、7年大厂程序员经历。全网粉丝15W、csdn博客专家、掘金/华为云//InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇dz…...
Zookeeper应用案例-分布式锁-实现思路
以下是具体实现代码 第一步:注册锁节点 第二步:获取锁节点,如果自己是最小的节点,就获取权限 第三步:拿到锁就开始自己的业务逻辑 第四步:业务逻辑好了就要释放这把锁 第五步:重新注册监听&…...
java练习(32)
ps:题目来自力扣 环形链表 给你一个链表的头节点 head ,判断链表中是否有环。 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表…...
伯克利 CS61A 课堂笔记 10 —— Trees
本系列为加州伯克利大学著名 Python 基础课程 CS61A 的课堂笔记整理,全英文内容,文末附词汇解释。 目录 01 Trees 树 Ⅰ Tree Abstraction Ⅱ Implementing the Tree Abstraction 02 Tree Processing 建树过程 Ⅰ Fibonacci tree Ⅱ Tree Process…...
让编程变成一种享受-明基RD320U显示器
引言 作为一名有着多年JAVA开发经验的从业者,在工作过程中,显示器的重要性不言而喻。它不仅是我们与代码交互的窗口,更是影响工作效率和体验的关键因素。在多年的编程生涯中,我遇到过各种各样的问题。比如,在进行代码…...
10分钟上手DeepSeek开发:SpringBoot + Vue2快速构建AI对话系统
作者:后端小肥肠 目录 1. 前言 为什么选择DeepSeek? 本文技术栈 2. 环境准备 2.1. 后端项目初始化 2.2. 前端项目初始化 3. 后端服务开发 3.1. 配置文件 3.2. 核心服务实现 4. 前端服务开发 4.1. 聊天组件ChatWindow.vue开发 5. 效果展示及源…...
LeetCode 0624.数组列表中的最大距离:只关心最小最大值
【LetMeFly】624.数组列表中的最大距离:只关心最小最大值 力扣题目链接:https://leetcode.cn/problems/maximum-distance-in-arrays/ 给定 m 个数组,每个数组都已经按照升序排好序了。 现在你需要从两个不同的数组中选择两个整数ÿ…...
如何解决服务器端口被攻击:全面防护与快速响应
服务器端口被攻击是网络安全中常见的问题之一,尤其是当服务器暴露在公共网络上时,容易成为黑客的目标。攻击者可能通过扫描开放端口、利用漏洞或发动拒绝服务(DoS/DDoS)攻击来破坏服务器的正常运行。本文将详细介绍如何检测、防御…...
Golang深度学习
前言 在2009年,Google公司发布了一种新的编程语言,名为Go(或称为Golang),旨在提高编程效率、简化并发编程,并提供强大的标准库支持。Go语言的设计者们希望通过Go语言能够解决软件开发中的一些长期存在的问…...
Linux环境开发工具
Linux软件包管理器yum Linux下安装软件方式: 源代码安装rpm安装——Linux安装包yum安装——解决安装源、安装版本、安装依赖的问题 yum对应于Windows系统下的应用商店 使用Linux系统的人:大部分是职业程序员 客户端怎么知道去哪里下载软件࿱…...
JupyterNotebook高级使用:常用魔法命令
%%writefile test.py def Test(name):print("Test",name,"success")运行结果:就是在我们的文件目录下面创建了这个test.py文件,主要是认识一下这个里面的%%writefile表示创建新的文件,这个文件里面的内容就是上面我们定义…...
C++ Primer 类的作用域
欢迎阅读我的 【CPrimer】专栏 专栏简介:本专栏主要面向C初学者,解释C的一些基本概念和基础语言特性,涉及C标准库的用法,面向对象特性,泛型特性高级用法。通过使用标准库中定义的抽象设施,使你更加适应高级…...
【建设工程经济】2.1-2.2 经济效果评价的相关概念及指标体系
一、学前建议 学习内容主要有: ①财务评价的内容:盈利能力分析、偿债能力分析、财务可持续能力分析(财务生存能力); ②经济效果评价方法分类:确定性和不确定性评价、定量分析和定性分析、静态分析和动态分…...
如何用ollama快速布署deepseek-r1大模型
deepseek在春节期间因为特朗普的一番发言而在中国已几乎人尽皆知,热度到了连90高寿的老父亲都向我推荐这个中国产的AI大模型,而且它是开源的!我试验了下,用ollama也可以快速度安装布署deepseek-r1大模型。本想写篇文章来介绍下dee…...
50页PDF|数字化转型成熟度模型与评估(附下载)
一、前言 这份报告依据GBT 43439-2023标准,详细介绍了数字化转型的成熟度模型和评估方法。报告将成熟度分为五个等级,从一级的基础转型意识,到五级的基于数据的生态价值构建与创新,涵盖了组织、技术、数据、资源、数字化运营等多…...
机器学习实战(8):降维技术——主成分分析(PCA)
第8集:降维技术——主成分分析(PCA) 在机器学习中,降维(Dimensionality Reduction) 是一种重要的数据处理技术,用于减少特征维度、去除噪声并提高模型效率。主成分分析(Principal C…...
面试编程题
1. 请写出string类的定义,要求有构造函数,析构函数,拷贝,赋值函数。 #include <cstring> #include <algorithm>class String { public:explicit String(const char* str nullptr){if(str){str_ new char[strlen(st…...
Transformer多头注意力并行计算原理与工业级实现:从数学推导到PyTorch工程优化
一、核心数学原理剖析 1.1 多头注意力矩阵分解 Q XW^Q ∈ R^{nd_k} K XW^K ∈ R^{nd_k} V XW^V ∈ R^{nd_v} 多头分解公式: head_i Attention(QW_i^Q, KW_i^K, VW_i^V) 其中 W_i^Q ∈ R^{d_kd_k/h}, W_i^K ∈ R^{d_kd_k/h}, W_i^V ∈ R^{d_vd_v/h} (h为头数…...
我的2025年计划
新春佳节已过去了,又是一年伊始,即将步入漫长的工作、生活中了。一年之计在于春,我也不能免俗。 本文从工作生活两方面,列出一些计划。到年底,再回过头来看看,有哪些实现了,有哪些未实现。 工作…...
软件开源与AI开源的区别
一.软件开源 软件开源是指软件的源代码对公众开放,允许用户自由使用、修改和分发的软件。 核心特性:低成本(通常免费)、高可定制性(源代码可用,开发人员可以修改)、社区支持(庞大的…...
前端插件使用xlsx-populate,花样配置excel内容,根据坐添加标替换excel内容,修改颜色,合并单元格...。
需求要求:业务人员有个非常复杂得excel表格,各种表头等,但是模板是固定得。当然也可以实现在excel上搞出各种表格,但是不如直接用已有模板替换其中要动态得内容方便,这里我们用到CSDN得 xlsx-populate 插件。 实列中我…...
分布式大语言模型服务引擎vLLM论文解读
论文地址:Efficient Memory Management for Large Language Model Serving with PagedAttention 摘要 大语言模型(LLMs)的高吞吐量服务需要一次对足够多的请求进行批处理。然而,现有系统面临困境,因为每个请求的键值…...
如何开发一个大模型应用?
1. 背景 AIGC技术的突破性进展彻底改变了技术开发的范式,尤其是以GPT为代表的LLM,凭借其强大的自然语言理解与生成能力,迅速成为全球科技领域的焦点。2023年末,随着ChatGPT的爆火,AIGC技术从实验室走向规模化应用&…...
01-零基础入门嵌入式系统
1.什么是嵌入式系统 首先我们要知道计算机系统分为大型机、通用计算机和嵌入式系统三大类。 计算机系统的发展,经历了由1台计算机系统为N个人服务的大型机时代到由1台计算机系统为1个人服务的PC时代,正在步入由N台计算机系统为1个人服务的嵌入式时代。 嵌…...
