【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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/22688.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用LangChain构建第一个ReAct Agent

使用LangChain构建第一个ReAct Agent 准备环境 使用Anaconda 安装python 3.10 安装langchain、langchain_openai、langchain_community &#xff08;安装命令 pip install XXX&#xff09; 申请DeepSeek API&#xff1a;https://platform.deepseek.com/api_keys&#xff08;也…

多人协同创作gitea

多人协同创作gitea 在多台设备上协同使用Gitea&#xff0c;主要是通过网络访问Gitea服务器上的仓库来进行代码管理和协作。以下是一些关键步骤和建议&#xff0c;帮助你在多台设备上高效地使用Gitea进行协作&#xff1a; 1. 确保Gitea服务可访问 首先&#xff0c;你需要确保…

【个人开源】——从零开始在高通手机上部署sd(二)

代码&#xff1a;https://github.com/chenjun2hao/qualcomm.ai 推理耗时统计 单位/ms 硬件qnncpu_clipqnncpu_unetqnncpu_vaehtp_cliphtp_unethtp_vae骁龙8 gen124716.994133440.39723.215411.097696.327 1. 下载依赖 下载opencv_x64.tar,提取码: rrbp下载opencv_aarch64.t…

SpringCloud系列教程:微服务的未来(二十五)-基于注解的声明队列交换机、消息转换器、业务改造

前言 在现代分布式系统中&#xff0c;消息队列是实现服务解耦和异步处理的关键组件。Spring框架提供了强大的支持&#xff0c;使得与消息队列&#xff08;如RabbitMQ、Kafka等&#xff09;的集成变得更加便捷和灵活。本文将深入探讨如何利用Spring的注解驱动方式来配置和管理队…

学习经验分享【39】YOLOv12——2025 年 2 月 19 日发布的以注意力为核心的实时目标检测器

YOLO算法更新速度很快&#xff0c;已经出到V12版本&#xff0c;后续大家有想发论文或者搞项目可更新自己的baseline了。 代码&#xff1a;GitHub - sunsmarterjie/yolov12: YOLOv12: Attention-Centric Real-Time Object Detectors 摘要&#xff1a;长期以来&#xff0c;增强 …

Pytorch实现之特征损失与残差结构稳定GAN训练,并训练自己的数据集

简介 简介:生成器和鉴别器分别采用了4个新颖设计的残差结构实现,同时在损失中结合了鉴别器层的特征损失来提高模型性能。 论文题目:Image Generation by Residual Block Based Generative Adversarial Networks(基于残留块的生成对抗网络产生图像) 会议:2022 IEEE Int…

后“智驾平权”时代,谁为安全冗余和体验升级“买单”

线控底盘&#xff0c;正在成为新势力争夺下一个技术普及红利的新赛点。 尤其是进入2025年&#xff0c;比亚迪、长安等一线传统自主品牌率先开启高阶智驾的普及战&#xff0c;加上此前已经普及的智能座舱&#xff0c;舱驾智能的「科技平权」进一步加速行业启动「线控底盘」上车窗…

【Node.js】express框架

目录 1初识express框架 2 初步使用 2.1 安装 2.2 创建基本的Web服务器 2.3 监听方法 2.3.1 监听get请求 2.3.2 监听post请求 2.4 响应客户端 2.5 获取url中的参数(get) 2.5.1 获取查询参数 2.5.2 获取动态参数 2.6 托管静态资源 2.6.1 挂载路径前缀 2.6.2 托管多…

树形DP(树形背包+换根DP)

树形DP 没有上司的舞会 家常便饭了&#xff0c;写了好几遍&#xff0c;没啥好说的&#xff0c;正常独立集问题。 int head[B]; int cnt; struct node {int v,nxt; }e[B<<1]; void modify(int u,int v) {e[cnt].nxthead[u];e[cnt].vv;head[u]cnt; } int a[B]; int f[B]…

REACT--组件通信

组件之间如何进行通信&#xff1f; 组件通信 组件的通信主要借助props传递值 分为整体接收、解构接收 整体接收 import PropTypes from prop-types;//子组件 function Welcome(props){return (<div>hello Welcome,{props.count},{props.msg}</div>) }// 对 We…

【排序算法】六大比较类排序算法——插入排序、选择排序、冒泡排序、希尔排序、快速排序、归并排序【详解】

文章目录 六大比较类排序算法&#xff08;插入排序、选择排序、冒泡排序、希尔排序、快速排序、归并排序&#xff09;前言1. 插入排序算法描述代码示例算法分析 2. 选择排序算法描述优化代码示例算法分析 3. 冒泡排序算法描述代码示例算法分析与插入排序对比 4. 希尔排序算法描…

纠错检索增广生成论文

一、摘要 动机&#xff1a;RAG严重依赖于检索文档的相关性&#xff0c;如果检索出错&#xff0c;那么LLM的输出结果也会出现问题 解决方案&#xff1a;提出纠正性检索增强生成&#xff08;CRAG&#xff09;即设计一个轻量级的检索评估器&#xff0c;用来评估针对某个查询检索…

Java NIO与传统IO性能对比分析

Java NIO与传统IO性能对比分析 在Java中&#xff0c;I/O&#xff08;输入输出&#xff09;操作是开发中最常见的任务之一。传统的I/O方式基于阻塞模型&#xff0c;而Java NIO&#xff08;New I/O&#xff09;引入了非阻塞和基于通道&#xff08;Channel&#xff09;和缓冲区&a…

easelog(1)基础C++日志功能实现

EaseLog(1)基础C日志功能实现 Author: Once Day Date: 2025年2月22日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 注&#xff1a;本简易日志组件代码实现参考了Google …

Vue面试2

1.跨域问题以及如何解决跨域 跨域问题&#xff08;Cross-Origin Resource Sharing, CORS&#xff09;是指在浏览器中&#xff0c;当一个资源试图从一个不同的源请求另一个资源时所遇到的限制。这种限制是浏览器为了保护用户安全而实施的一种同源策略&#xff08;Same-origin p…

MongoDB应用设计调优

应用范式设计 什么是范式 数据库范式概念是数据库技术的基本理论&#xff0c;几乎是伴随着数据库软件产品的推出而产生的。在传统关系型数据库领域&#xff0c;应用开发中遵循范式是最基本的要求。但随着互联网行业的发展&#xff0c;NoSQL开始变得非常流行&#xff0c;在许多…

Mac安装配置Tomcat 8

一、官网下载 Index of /disthttps://archive.apache.org/dist/ 1、进入界面如下&#xff1a; 2、我们找到Tomcat文件夹并进入 3、找到Tomcat 8并打开 4、找到对应的版本打开 5、打开bin 6、找到“apache-tomcat-8.5.99.tar.gz”并下载 二、配置Tomcat 1、解压已经下载好的…

【论文精读】VLM-AD:通过视觉-语言模型监督实现端到端自动驾驶

论文地址&#xff1a; VLM-AD: End-to-End Autonomous Driving through Vision-Language Model Supervision 摘要 人类驾驶员依赖常识推理来应对复杂多变的真实世界驾驶场景。现有的端到端&#xff08;E2E&#xff09;自动驾驶&#xff08;AD&#xff09;模型通常被优化以模仿…

百度搜索,能否将DeepSeek变成“内功”?

最近&#xff0c;所有的云平台和主流APP都在努力接入DeepSeek。其中&#xff0c;搜索类APP与搜索引擎更是“战况激烈”。那么问题来了&#xff0c;接入DeepSeek已经变成了标准配置&#xff0c;到底应该如何做出差异化&#xff1f;接入DeepSeek这件事能不能实现11大于2的效果&am…

Flask实现高效日志记录模块

目录 一. 简介&#xff1a; 1. 为什么需要请求日志 二. 日志模块组成 1. 对应日志表创建&#xff08;包含日志记录的关键字段&#xff09; 2. 编写日志记录静态方法 3. 在Flask中捕获请求日志 4. 捕获异常并记录错误日志 5. 编写日志接口数据展示 6. 写入数据展…