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

【Linux进程】基于管道实现进程池

目录

前言

 1. 进程池

   1.1 基本结构:

1.2. 池化技术

 1.3. 思路分析

1.4. 代码实现

总结


前言

        上篇文章介绍了管道及其使用,本文在管道的基础上,通过匿名管道来实现一个进程池;

在这里插入图片描述

 1. 进程池

 父进程创建一组子进程,子进程根据父进程的发送的信号,来做出相应的操作;

   1.1 基本结构:

 master为父进程,父进程通过管道向子进程发送对应的信号,让子进程执行相关的操作;

1.2. 池化技术

为什么要有进程池?
        要解答这个问题,需要先了解池化技术;池化技术是一种常见的优化方法,可用于提高计算和存储资源的利用率,从而提高系统性能。通过分类和管理资源或任务的池,可以实现资源的高效共享和复用;

        举个最简单的例子:我们在写vector时,它有一个扩容操作,我们在实现时一般是2倍扩容,为什么要多扩容?——为了防止频繁的申请空间;池化技术也是类似的优化方法,通过一次性申请一定数量的资源,然后自己管理这些资源的分配和回收,从而减少频繁向操作系统申请资源的次数在操作系统中,申请空间、创建进程等操作都需要一定的时间开销。因此,频繁地进行这些操作会降低系统的效率;
        进程池会提前创建一定数量的进程并保存在进程池中,当需要使用新的进程时,可以直接从进程池中获取已经存在的空闲进程来执行任务,而不需要每次都创建新的进程,从而减少了创建和销毁进程的开销;

注意:

         在实现上,把任务分配给不同的信道一定要平均,不能是有的信道很忙,有的信道很闲,这样也无法提高效率;

 在此之前,为了便于理解,这里再次回顾一下管道的特点,及几种不同的情况:

a. 管道的4种情况

  1. 正常情况,如果管道没有数据了,读端必须等待,直到有数据为止(写端写入数据了)
  2. 正常情况,如果管道被写满了,写端必须等待,直到有空间为止(读端读走数据)
  3. 写端关闭,读端一直读取, 读端会读到read返回值为0, 表示读到文件结尾
  4. 读端关闭,写端一直写入,OS会直接杀掉写端进程,通过想目标进程发送SIGPIPE(13)信号,终止目标进程

b. 匿名管道的5种特性

  1. 匿名管道,可以允许具有血缘关系的进程之间进行进程间通信,常用与父子,仅限于此
  2. 匿名管道,默认给读写端要提供同步机制 --- 了解现象就行
  3. 面向字节流的 --- 了解现象就行
  4. 管道的生命周期是随进程的
  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 基本结构&#xff1a; 1.2. 池化技术 1.3. 思路分析 1.4. 代码实现 总结 前言 上篇文章介绍了管道及其使用&#xff0c;本文在管道的基础上&#xff0c;通过匿名管道来实现一个进程池&#xff1b; 1. 进程池 父进程创建一组子进程&#xff0c;子进…...

软件测试之单元测试

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

vscode+编程AI配置、使用说明

文章目录 [toc]1、概述2、github copilot2.1 配置2.2 使用文档2.3 使用说明 3、文心快码&#xff08;Baidu Comate&#xff09;3.1 配置3.2 使用文档3.3 使用说明 4、豆包&#xff08;MarsCode&#xff09;4.1 配置4.2 使用文档4.3 使用说明 5、通义灵码&#xff08;TONGYI Lin…...

007-spring-bean的相关配置(重要)

spring-bean的相关配置...

【唐叔学算法】第19天:交换排序-冒泡排序与快速排序的深度解析及Java实现

引言 排序算法是计算机科学中的基础问题&#xff0c;而交换排序作为其中一类经典的排序方法&#xff0c;因其简单直观的思想和易于实现的特点&#xff0c;在初学者中广受欢迎。交换排序的核心思想是通过不断交换相邻元素来达到排序的目的。本文将深入探讨两种典型的交换排序算…...

合并 Python 中的字典

合并 Python 中的字典 如何在 Python 中合并字典&#xff1f; 这取决于你对“合并”一词的具体定义。 在 Python 中使用 | 操作符合并字典 首先&#xff0c;让我们讨论合并字典的最简单方法&#xff0c;这通常已经足够满足你的需求。 以下是两个字典&#xff1a; >>…...

使用Python实现自动化文档生成工具:提升文档编写效率的利器

友友们好! 我的新专栏《Python进阶》正式启动啦!这是一个专为那些渴望提升Python技能的朋友们量身打造的专栏,无论你是已经有一定基础的开发者,还是希望深入挖掘Python潜力的爱好者,这里都将是你不可错过的宝藏。 在这个专栏中,你将会找到: ● 深入解析:每一篇文章都将…...

uniapp使用live-pusher实现模拟人脸识别效果

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

【JavaSE】【网络原理】初识网络

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

鸿蒙之路的坑

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

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 结构详解&#xff1a; http 块&#xff1a;用于配置 HTTP 服务器的相关设置&#xff0c;包括处理 HTTP 和 HTTPS。 stream 块&#xff1a;用于配置 TCP/UDP 代理服务器&#xff0c;适用于需要进行四层负载均衡的情况。 ... # 全局块 events {...} …...

shardingsphere分库分表项目实践4-sql解析sql改写

为什么要sql解析重写&#xff1f; 如果我们的系统数据库实现了分表&#xff0c;那么我们的sql中表名需要根据参数动态确定&#xff0c;那么代码怎么写&#xff1f; 方案1&#xff1a; 自己手动拼接&#xff0c; 比如 update t_user_${suffix} , ${suffix} 作为一个变量传递…...

mysql数据库中,一棵3层的B+树,假如数据节点大小是1k,那这棵B+可以存多少条记录(2100万的由来)

在MySQL中&#xff0c;3层的B树可以存储的数据量取决于多个因素&#xff0c;包括页大小、每行数据的大小以及索引项的大小。以下是一个详细的计算过程&#xff1a; 一、假设条件 页大小&#xff1a;在InnoDB存储引擎中&#xff0c;B树的每个节点&#xff08;页&#xff09;大…...

Git 操作全解:从基础命令到高级操作的实用指南

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

华院计算参与项目再次被《新闻联播》报道

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

从一次线上故障聊聊接口自动化测试

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

Element-ui的使用教程 基于HBuilder X

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

Chapter 03 复合数据类型-1

1.列表 Python内置的一种有序、可变的序列数据类型&#xff1b; 列表的定义&#xff1a; [ ]括起来的逗号分隔的多个元素组成的序列 列表对象的创建&#xff1a; &#xff08;1&#xff09;直接赋值 >>> list1 []#创建一个空列表赋值给list1 >>> list…...

【Python知识】Python面向对象编程知识

Python面向对象编程知识 概述1. 类&#xff08;Class&#xff09;2. 对象&#xff08;Object&#xff09;3. 封装&#xff08;Encapsulation&#xff09;4. 继承&#xff08;Inheritance&#xff09;5. 多态&#xff08;Polymorphism&#xff09;6. 抽象&#xff08;Abstractio…...

Python爬虫实战:研究MechanicalSoup库相关技术

一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...

AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月05日更新到&#xff1a; AI炼丹日志-28 - Aud…...

聊聊 Pulsar:Producer 源码解析

一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台&#xff0c;以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中&#xff0c;Producer&#xff08;生产者&#xff09; 是连接客户端应用与消息队列的第一步。生产者…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢

随着互联网技术的飞速发展&#xff0c;消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁&#xff0c;不仅优化了客户体验&#xff0c;还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用&#xff0c;并…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?

论文网址&#xff1a;pdf 英文是纯手打的&#xff01;论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误&#xff0c;若有发现欢迎评论指正&#xff01;文章偏向于笔记&#xff0c;谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...

稳定币的深度剖析与展望

一、引言 在当今数字化浪潮席卷全球的时代&#xff0c;加密货币作为一种新兴的金融现象&#xff0c;正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而&#xff0c;加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下&#xff0c;稳定…...

USB Over IP专用硬件的5个特点

USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中&#xff0c;从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备&#xff08;如专用硬件设备&#xff09;&#xff0c;从而消除了直接物理连接的需要。USB over IP的…...

【VLNs篇】07:NavRL—在动态环境中学习安全飞行

项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战&#xff0c;克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...

【JVM面试篇】高频八股汇总——类加载和类加载器

目录 1. 讲一下类加载过程&#xff1f; 2. Java创建对象的过程&#xff1f; 3. 对象的生命周期&#xff1f; 4. 类加载器有哪些&#xff1f; 5. 双亲委派模型的作用&#xff08;好处&#xff09;&#xff1f; 6. 讲一下类的加载和双亲委派原则&#xff1f; 7. 双亲委派模…...

作为测试我们应该关注redis哪些方面

1、功能测试 数据结构操作&#xff1a;验证字符串、列表、哈希、集合和有序的基本操作是否正确 持久化&#xff1a;测试aof和aof持久化机制&#xff0c;确保数据在开启后正确恢复。 事务&#xff1a;检查事务的原子性和回滚机制。 发布订阅&#xff1a;确保消息正确传递。 2、性…...