【Linux进程】基于管道实现进程池
目录
前言
1. 进程池
1.1 基本结构:
1.2. 池化技术
1.3. 思路分析
1.4. 代码实现
总结
前言
上篇文章介绍了管道及其使用,本文在管道的基础上,通过匿名管道来实现一个进程池;
1. 进程池
父进程创建一组子进程,子进程根据父进程的发送的信号,来做出相应的操作;
1.1 基本结构:
master为父进程,父进程通过管道向子进程发送对应的信号,让子进程执行相关的操作;
1.2. 池化技术
为什么要有进程池?
要解答这个问题,需要先了解池化技术;池化技术是一种常见的优化方法,可用于提高计算和存储资源的利用率,从而提高系统性能。通过分类和管理资源或任务的池,可以实现资源的高效共享和复用;
举个最简单的例子:我们在写vector时,它有一个扩容操作,我们在实现时一般是2倍扩容,为什么要多扩容?——为了防止频繁的申请空间;池化技术也是类似的优化方法,通过一次性申请一定数量的资源,然后自己管理这些资源的分配和回收,从而减少频繁向操作系统申请资源的次数在操作系统中,申请空间、创建进程等操作都需要一定的时间开销。因此,频繁地进行这些操作会降低系统的效率;
进程池会提前创建一定数量的进程并保存在进程池中,当需要使用新的进程时,可以直接从进程池中获取已经存在的空闲进程来执行任务,而不需要每次都创建新的进程,从而减少了创建和销毁进程的开销;
注意:
在实现上,把任务分配给不同的信道一定要平均,不能是有的信道很忙,有的信道很闲,这样也无法提高效率;
在此之前,为了便于理解,这里再次回顾一下管道的特点,及几种不同的情况:
a. 管道的4种情况
- 正常情况,如果管道没有数据了,读端必须等待,直到有数据为止(写端写入数据了)
- 正常情况,如果管道被写满了,写端必须等待,直到有空间为止(读端读走数据)
- 写端关闭,读端一直读取, 读端会读到read返回值为0, 表示读到文件结尾
- 读端关闭,写端一直写入,OS会直接杀掉写端进程,通过想目标进程发送SIGPIPE(13)信号,终止目标进程
b. 匿名管道的5种特性
- 匿名管道,可以允许具有血缘关系的进程之间进行进程间通信,常用与父子,仅限于此
- 匿名管道,默认给读写端要提供同步机制 --- 了解现象就行
- 面向字节流的 --- 了解现象就行
- 管道的生命周期是随进程的
- 管道是单向通信的,半双工通信的一种特殊情况
1.3. 思路分析
要创建一个管道用于父进程于子进程的通信简单:
问题在于后续管道的创建:
结构图如下:
在创建第二个管道时,父进程新建子进程,子进程继承父进程的属性;然后关闭父进程的读端,子进程的写端,构建单向信道;
问题就出在这里,看上图结构:子进程会继承父进程属性,所以第二个子进程的3号文件描述符也会指向的1号管道的写端(正常情况下是不能指定的);
以此类推,第三个进程也会指向1号管道和2号管道;只有最后一个管道,是只有一个写端指向,其余的管道都有多个写端;
在实际上不会出现子进程向管道写入的情况,但是在关闭管道的时候容易出问题;不注意就会导致程序阻塞;
正常的关闭信道这样写:
for (const auto& e : c)
{close(e.ctrlfd);waitpid(e.workerid, nullptr, 0);
}
遍历这个数组,关闭父进程对每个管道的写端;看似很完美,而实际情况是:
除最后一个管道,其余管道依然会有写端指向,而只有当所有写端都关闭后,调用read函数时,才会返回0,表示已经读取到文件末尾;此时表示执行完毕,会进入下一步的回收;
如果存在写端,并且写端一直不写入数据,在这种情况下,read函数不会返回而是一直等待数据到来。读端(子进程)的read函数会一直阻塞,直到有数据写入为止;这也就会导致程序阻塞住;
如何解决?
方法一:
倒着遍历去关闭管道;
倒着去关闭,最后一个管道没有写端,管道正常关闭,回收子进程,子进程被回收,文件描述符也会被释放,其余管道的写端就会减少;依次关闭,就可以保证所有管道正常关闭;
方法二:
借助数据结构去解决,记录父进程中的写端fd,在子进程中依次关闭;这样创建出来的信道之间连接关系是理想的,关系不会那么乱;
父进程中创建一个临时vector,父进程记录每个管道写端fd,把数据存储道临时vector中一份;这样每个子进程拿到的就是当前父进程所有写端的fd,子进程全部关掉即可;
1.4. 代码实现
模拟执行任务,编写一个任务类:
// Task.hpp
#pragma once#include <iostream>
#include <functional>
#include <vector>
#include <ctime>typedef std::function<void()> task_t;
void Download()
{std::cout << "我是一个下载任务"<< " 处理者: " << getpid() << std::endl;
}void PrintLog()
{std::cout << "我是一个打印日志的任务"<< " 处理者: " << getpid() << std::endl;
}
void PushVideoStream()
{std::cout << "这是一个推送视频流的任务"<< " 处理者: " << getpid() << std::endl;
}class Tasklist
{public:Tasklist(){tasks.push_back(Download);tasks.push_back(PrintLog);tasks.push_back(PushVideoStream);// 模拟随机任务srand(time(nullptr));}// 检查信号是否合法bool CheckSafe(int code){if (code >= 0 && code < tasks.size())return true;elsereturn false;}// 执行任务void RunTask(int code){return tasks[code]();}// 随机选择任务int SelectTask(){return rand() % tasks.size();}// 信号转换std::string ToDesc(int code){switch (code){case g_download_code:return "Download";case g_printlog_code:return "PrintLog";case g_push_videostream_code:return "PushVideoStream";default:return "Unknow";}}
public:const static int g_download_code = 0;const static int g_printlog_code = 1;const static int g_push_videostream_code = 2;std::vector<task_t> tasks;
};Tasklist init;//定义对象
进程池:
#include <iostream>
#include <vector>
#include <string>
#include <unistd.h>
#include <assert.h>
#include <sys/wait.h>
#include <sys/types.h>
#include "Task.hpp"const int nums = 5;
static int number = 1;// 将管道描述成一个对象
class channel
{
public:channel(int fd, pid_t id):ctrlfd(fd), workerid(id){name = "channel " + std::to_string(number++);}
public:int ctrlfd;pid_t workerid;//进程idstd::string name;};
// 工作接口
void work()
{while (true){int code = 0;// 该接口由子进程执行// 从标准输入去读(其实就是从管道去读)// 为了方便读数据,所以将对应管道读端,重定向到标准输入,否则还需记录子进程对应管道的读端ssize_t n = read(0, &code, sizeof(code));//assert(n == sizeof(code)); //父进程一旦退出,子进程也会退出,没有数据写入,读到的字节是0,assert强制停止;// 判断读到的数据是否正常if (n == sizeof(code)){if (!init.CheckSafe(code)) continue;init.RunTask(code);}else if (n == 0){break;}else{}}std::cout << "child quit " << std::endl;
}void PrintFd(const std::vector<int>& fds)
{std::cout << getpid() << " close fds: ";for (auto fd : fds){std::cout << fd << " ";}std::cout << std::endl;
}// 创建管道
void CreateChannel(std::vector<channel>* c)
{std::vector<int> tmp;for (int i = 0; i < nums; i++){// 创建信道int pipefd[2];int n = pipe(pipefd);//管道建立成功返回0,失败返回错误码assert(n == 0);(void)n;// 创建进程pid_t id = fork();assert(id >= 0); // 条件为假返回报错信息// 构建单向信道if (id == 0) //child{if (!tmp.empty()){// 方法二// 关闭其余写端的fdfor (auto& e : tmp){close(e);}PrintFd(tmp);}// 这里并没有重复关闭,先创建的管道,再创建子进程,然后再关闭将写端fd加入到tmp// 这里是关闭子进程对当前管道的写端close(pipefd[1]);// 将标准输入重定向到管道读端,管道从标准输入中读数据dup2(pipefd[0], 0);// TODOwork();exit(0);}// fatherclose(pipefd[0]); //关闭父进程读// 将管道存储起来方便后续管理c->push_back(channel(pipefd[1], id));tmp.push_back(pipefd[1]);}
}void SendCommand(const std::vector<channel>& c, bool flag, int nums = -1)
{int pos = 0;while (true){//1.选择任务int command = init.SelectTask();//2.选择信道const auto& channel = c[pos++];pos %= c.size();//debugstd::cout << "send command " << init.ToDesc(command) << "[" << command << "]"<< " in "<< channel.name << " worker is : " << channel.workerid << std::endl;//3.发送任务write(channel.ctrlfd, &command, sizeof(command));// 判断任务执行完是否退出if (!flag){nums--;if (nums <= 0)break;}sleep(1);}std::cout << "SendCommand done..." << std::endl;
}
void ReleaseChannel(std::vector<channel>& c)
{//倒着回收// int n = c.size() - 1;// for(; n >= 0; n--)// {// close(c[n].ctrlfd);// waitpid(c[n].workerid, nullptr, 0);// }for (const auto& e : c){close(e.ctrlfd);waitpid(e.workerid, nullptr, 0);}
}const bool g_always_loop = true;
int main()
{std::vector<channel> channels;//创建进程创建信道CreateChannel(&channels);//开始执行任务SendCommand(channels, !g_always_loop, 10);//回收资源等待子进程退出ReleaseChannel(channels);return 0;
}
结构并不复杂,这里只是一个简单的示例;这个示例比较考验对多进程编程;
1.5. 思考
在进程池体系中,如果一个子进程退出了(一个管道的读端关闭)会怎样?
如果父进程知道子进程已经退出,即通过监控子进程状态并处理子进程退出的情况下,父进程在得知子进程退出后就不会再继续向已经退出的子进程的管道中写入数据也不会因为收到信号而终止
如果父进程没有正确地监控子进程的状态,不知道子进程已经退出,那么当父进程尝试向已经退出的子进程的管道中写入数据时,会收到SIGPIPE信号而终止。在这种情况下,剩余的子进程会成为孤儿进程,由操作系统接管;
因此在设计时也可以进行特殊处理,处理子进程退出,避免父进程被OS杀死的情况;如何去处理?
可以在选择信道那里多一步判断,判断信道对应的子进程是否已经退出,通过信道描述类,找到对应的进程id,通过waitpid的非阻塞等待判断是否退出;
总结
以上便是本文的全部内容,希望对你有所帮助或启发,感谢阅读!
相关文章:

【Linux进程】基于管道实现进程池
目录 前言 1. 进程池 1.1 基本结构: 1.2. 池化技术 1.3. 思路分析 1.4. 代码实现 总结 前言 上篇文章介绍了管道及其使用,本文在管道的基础上,通过匿名管道来实现一个进程池; 1. 进程池 父进程创建一组子进程,子进…...

软件测试之单元测试
🍅 点击文末小卡片,免费获取软件测试全套资料,资料在手,涨薪更快 一、何为单测 测试有黑盒测试和白盒测试之分,黑盒测试顾名思义就是我们不了解盒子的内部结构,我们通过文档或者对该功能的理解,…...

vscode+编程AI配置、使用说明
文章目录 [toc]1、概述2、github copilot2.1 配置2.2 使用文档2.3 使用说明 3、文心快码(Baidu Comate)3.1 配置3.2 使用文档3.3 使用说明 4、豆包(MarsCode)4.1 配置4.2 使用文档4.3 使用说明 5、通义灵码(TONGYI Lin…...

007-spring-bean的相关配置(重要)
spring-bean的相关配置...
【唐叔学算法】第19天:交换排序-冒泡排序与快速排序的深度解析及Java实现
引言 排序算法是计算机科学中的基础问题,而交换排序作为其中一类经典的排序方法,因其简单直观的思想和易于实现的特点,在初学者中广受欢迎。交换排序的核心思想是通过不断交换相邻元素来达到排序的目的。本文将深入探讨两种典型的交换排序算…...
合并 Python 中的字典
合并 Python 中的字典 如何在 Python 中合并字典? 这取决于你对“合并”一词的具体定义。 在 Python 中使用 | 操作符合并字典 首先,让我们讨论合并字典的最简单方法,这通常已经足够满足你的需求。 以下是两个字典: >>…...
使用Python实现自动化文档生成工具:提升文档编写效率的利器
友友们好! 我的新专栏《Python进阶》正式启动啦!这是一个专为那些渴望提升Python技能的朋友们量身打造的专栏,无论你是已经有一定基础的开发者,还是希望深入挖掘Python潜力的爱好者,这里都将是你不可错过的宝藏。 在这个专栏中,你将会找到: ● 深入解析:每一篇文章都将…...

uniapp使用live-pusher实现模拟人脸识别效果
需求: 1、前端实现模拟用户人脸识别,识别成功后抓取视频流或认证的一张静态图给服务端。 2、服务端调用第三方活体认证接口,验证前端传递的人脸是否存在,把认证结果反馈给前端。 3、前端根据服务端返回的状态,显示在…...

【JavaSE】【网络原理】初识网络
目录 一、网络互联二、局域网与广域网三、网络通信基础3.1 IP地址3.2 端口号3.3 网络协议3.4 五元组 四、协议分层4.1 OSI七层网络模型4.2 TCP/IP五层(四层)网络模型4.3 网络设备 五、网络数据通信基本流程。5.1 封装和分用5.2 简述过程 一、网络互联 网络互联: 网…...

鸿蒙之路的坑
1、系统 Windows 10 家庭版不可用模拟器 对应的解决方案【坑】 升级系统版本 直接更改密钥可自动升级系统 密钥找对应系统的(例:windows 10专业版) 升级完之后要激活 坑1、升级完后事先创建好的模拟器还是无法启动 解决:删除模拟…...

Python生日祝福烟花
1. 实现效果 2. 素材加载 2个图片和3个音频 shoot_image pygame.image.load(shoot(已去底).jpg) # 加载拼接的发射图像 flower_image pygame.image.load(flower.jpg) # 加载拼接的烟花图 烟花不好去底 # 调整图像的像素为原图的1/2 因为图像相对于界面来说有些大 shoo…...
Ubuntu环境 nginx.conf详解(二)
1、nginx.conf 结构详解: http 块:用于配置 HTTP 服务器的相关设置,包括处理 HTTP 和 HTTPS。 stream 块:用于配置 TCP/UDP 代理服务器,适用于需要进行四层负载均衡的情况。 ... # 全局块 events {...} …...

shardingsphere分库分表项目实践4-sql解析sql改写
为什么要sql解析重写? 如果我们的系统数据库实现了分表,那么我们的sql中表名需要根据参数动态确定,那么代码怎么写? 方案1: 自己手动拼接, 比如 update t_user_${suffix} , ${suffix} 作为一个变量传递…...
mysql数据库中,一棵3层的B+树,假如数据节点大小是1k,那这棵B+可以存多少条记录(2100万的由来)
在MySQL中,3层的B树可以存储的数据量取决于多个因素,包括页大小、每行数据的大小以及索引项的大小。以下是一个详细的计算过程: 一、假设条件 页大小:在InnoDB存储引擎中,B树的每个节点(页)大…...

Git 操作全解:从基础命令到高级操作的实用指南
文章目录 1.基本命令1.初始化仓库2.克隆远程仓库3.查看当前仓库状态4.查看提交日志5.添加文件到暂存区6.提交更改7.查看仓库的配置信息 2.分支操作1.查看所有分支2.创建新分支3.切换名称4.创建并切换到新分支5.删除分支6.查看当前分支 3.合并分支1.合并分支2.解决合并冲突 4.远…...

华院计算参与项目再次被《新闻联播》报道
12月17日,央视《新闻联播》播出我国推进乡村振兴取得积极进展。其中,华院计算参与的江西省防止返贫监测帮扶大数据系统被报道,该系统实现了由原来的“人找人”向“数据找人”的转变,有效提升监测帮扶及时性和有效性,守…...

从一次线上故障聊聊接口自动化测试
1、背景 3月初,运营同事配置了个还未上线的页面到网站首页 banner,导致用户点了报错。尽管这次很明确是运营人为操作失误引起的故障,但过往此类核心页面的访问异常,我们已不是第一次遇见。 从平台整体利益触发,我们各…...

Element-ui的使用教程 基于HBuilder X
文章目录 1.Element-ui简介2.使用HBuilderX 创建一个基于Vue3的项目 (由于是使用的基于Vue3的Element-ui)3.安装element-ui4.在项目里完全引用element-ui5.引用组件6.运行项目 1.Element-ui简介 Element,一套为开发者、设计师和产品经理准备…...

Chapter 03 复合数据类型-1
1.列表 Python内置的一种有序、可变的序列数据类型; 列表的定义: [ ]括起来的逗号分隔的多个元素组成的序列 列表对象的创建: (1)直接赋值 >>> list1 []#创建一个空列表赋值给list1 >>> list…...
【Python知识】Python面向对象编程知识
Python面向对象编程知识 概述1. 类(Class)2. 对象(Object)3. 封装(Encapsulation)4. 继承(Inheritance)5. 多态(Polymorphism)6. 抽象(Abstractio…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...
MySQL JOIN 表过多的优化思路
当 MySQL 查询涉及大量表 JOIN 时,性能会显著下降。以下是优化思路和简易实现方法: 一、核心优化思路 减少 JOIN 数量 数据冗余:添加必要的冗余字段(如订单表直接存储用户名)合并表:将频繁关联的小表合并成…...
安卓基础(Java 和 Gradle 版本)
1. 设置项目的 JDK 版本 方法1:通过 Project Structure File → Project Structure... (或按 CtrlAltShiftS) 左侧选择 SDK Location 在 Gradle Settings 部分,设置 Gradle JDK 方法2:通过 Settings File → Settings... (或 CtrlAltS)…...
小木的算法日记-多叉树的递归/层序遍历
🌲 从二叉树到森林:一文彻底搞懂多叉树遍历的艺术 🚀 引言 你好,未来的算法大神! 在数据结构的世界里,“树”无疑是最核心、最迷人的概念之一。我们中的大多数人都是从 二叉树 开始入门的,它…...

保姆级【快数学会Android端“动画“】+ 实现补间动画和逐帧动画!!!
目录 补间动画 1.创建资源文件夹 2.设置文件夹类型 3.创建.xml文件 4.样式设计 5.动画设置 6.动画的实现 内容拓展 7.在原基础上继续添加.xml文件 8.xml代码编写 (1)rotate_anim (2)scale_anim (3)translate_anim 9.MainActivity.java代码汇总 10.效果展示 逐帧…...

Axure 下拉框联动
实现选省、选完省之后选对应省份下的市区...

实战设计模式之模板方法模式
概述 模板方法模式定义了一个操作中的算法骨架,并将某些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的前提下,重新定义算法中的某些步骤。简单来说,就是在一个方法中定义了要执行的步骤顺序或算法框架,但允许子类…...
Pydantic + Function Calling的结合
1、Pydantic Pydantic 是一个 Python 库,用于数据验证和设置管理,通过 Python 类型注解强制执行数据类型。它广泛用于 API 开发(如 FastAPI)、配置管理和数据解析,核心功能包括: 数据验证:通过…...

Matlab实现任意伪彩色图像可视化显示
Matlab实现任意伪彩色图像可视化显示 1、灰度原始图像2、RGB彩色原始图像 在科研研究中,如何展示好看的实验结果图像非常重要!!! 1、灰度原始图像 灰度图像每个像素点只有一个数值,代表该点的亮度(或…...