Linux——进程池(管道)

经过了管道的介绍之后,我们可以实现了进程间通信,现在我就来简单介
绍一下管道的应用场景——进程池。

1. 引入

在我们的编码过程中,不乏会听到,内存池,进程池,空间配置器等等名词,这些是用来干嘛的呢?
我们在自己写一个顺序表等容器的时候,我们的容器的容量的扩容不是需要一个我们就开一个,而是以整数倍,开辟内存。这样做的好处是,我们在使用的顺序表的时候可以一定程度上减少扩容的消耗(数据迁移,函数调用)。提高我们代码的效率。这样我们的资源就像一个池子一样,用之即取。这也是池化技术的思想。而我们的进程池也是一样,有的时候我们的主进程再进行一些任务的时候,需要创建一个新进程来执行这些任务,但是如果是在执行任务的时候在创建进程的话,会降低代码的整体效率,所以对于这样的场景,我们就可以提前创建好所需要的进程来供主进程使用。这也是利用了池化的思想。
所以池化的思想也就是指预先分配一定数量的资源,然后在需要时动态地从资源池中获取资源,使用完毕后再将资源归还到资源池中,以提高资源利用率和系统性能。
而我们进程池要实现首先就需要实现进程间通信的功能。如果没有这一点功能,我们的进程如何分配给子进程任务。

2. 进程池的创建

a. 信道以及子进程的创建

所以我们现在来编写一首简单的进程池:
上面也说了,要实现进程池首先就得有进程间通信的功能,所以我们这里选择使用匿名管道,并且假设我们需要五个子进程来执行任务:
首先我们需要能够创建出信道和子进程:

#include <iostream>
#include <cstring>
#include <unistd.h>using namespace std;int main()
{// 创建与子进程通信的管道(信道)int pipefd[2];int piperet = pipe(pipefd);if(piperet < 0){cout << "erron: " << errno << " error: " << strerror(errno) << endl;return 1;}// 创建子进程,建立信道pid_t id = fork();if(id < 0){cout << "erron: " << errno << " error: " << strerror(errno) << endl;return 1;}else if(id == 0){// 子进程close(pipefd[1]); // 关闭写端exit(0);}// 父进程close(pipefd[0]); // 关闭读端return 0;
}

在此基础上我们添加个循环不就可以,创建多个子进程了吗:
在这里插入图片描述
但是现在就有一个问题,我们所使用的pipefd数组在循环中,出了作用域它就不存在了,那么我们还怎么给子进程分配任务呢?其实我们使用一个数组存储相关数据就可以了,那存储什么呢?只存一个文件描述符可以吗?我们现在是一个主进程,当我们子进程们分配任务时,我们需不需要知道它是哪个进程,fd是让操作系统来认识的,我们是程序员,我们可以对子进程也就是信道命名啊,这样的特定的进程做特定的事不久也可以实现了嘛。既然它是子进程的话,那是不是应该再纪录它的pid啊。所以我们的存储对象应该是一个结构体,它其中有父进程写端对应的fd,信道名,子进程的pid:
在这里插入图片描述
在这里插入图片描述
插入一个数组中,供父进程方便分配。

b. 执行任务

现在我们可以,让子进程执行任务了,我们的假设是通过某种映射关系,能够让子进程知道进行哪些任务,而这种映射关系的key是固定四字节大小的,执行任务也是一直需要循环的:
在这里插入图片描述

c. 任务的构建

现在我们有了任务的执行,我们可以创建几个形式上的任务:
在这里插入图片描述
我们说了会以映射的方式来让子进程明确子进程执行的是哪个任务,在这里我们要让一个函数和数字有了某种映射关系,在这里我们使用这种方式:
在这里插入图片描述

这样我们就建立起了任务与整数的关系。
还需要一个接口,并且我们设计分配任务目前是随机的,所以还需要一个随机数种子:
在这里插入图片描述

d. 任务的选择与信道的选择

这里我们任务的选择使用随机数选择,信道的选择使用循环的方式来选择。实际场景中任务的选择和信道的选择肯定是根据每个进程的任务完成状态来实行任务的分配的,这里我们就简单一点:
在这里插入图片描述
在这里插入图片描述
然后稍稍优化一下:
在这里插入图片描述
在这里插入图片描述
我们就可以看到这样的效果:
在这里插入图片描述

e. 子进程的回收

我们知道当对于管道通信而言,写端关闭,那么读端的read函数会一直返回0。利用这一点,我们可以实现对子进程的回收:
在这里插入图片描述
我们这时候对代码改造一下,让它执行到一定次数后结束分配任务:
在这里插入图片描述
然后我们,执行代码测试一下:
在这里插入图片描述
我们发现它卡住了,我们并没有使用休眠函数,其实这是有原因的,我们再来看看这么一段回收子进程的代码:
在这里插入图片描述
再来执行一次:
在这里插入图片描述
回收成功了,我来解释一下其中的原因:
我们的主进程创建好第一个子进程的时候应该是这样:
在这里插入图片描述
这时候,我们再创建第二个进程:
在这里插入图片描述
可以看到第二个子进程的文件描述符表中有一个指向了第一个信道。而我们回收进程的时候,我们第一个回收的是第一个进程,我们关闭了主进程的4号文件,但是仍有文件指针指向信道一号以写的方式,所以这个时候我们,这时候要回收第一个子进程,第一个子进程就没有退出,所以会一直阻塞在回收第一个子进程的代码上,所以会卡了。而处理这个的办法也不难:
第一中方式就是,先关闭所有写端,那所有进程都会退出,这个时候我们再回收进程:
在这里插入图片描述
还有一种方式就是,倒着回收进程,它不能回收的原因不就是后续创建进程时,后面的进程中的文件描述符表中会指向前面的信道嘛:
在这里插入图片描述

在这里插入图片描述
还有一种方式就是在创建进程的时候,我们可以记录下主进程的写端fd,然后再后续创建子进程的时候给他关闭就可以了,这样我们就可以直接回收子进程了:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
以上就是一个简略版的进程池,我为了阐述方便没有过度的进行封装,有兴趣的小伙伴可以自行封装一手。全部代码如下:

#include <iostream>
#include <functional>
#include <ctime>
#include <cassert>
#include <string>
#include <vector>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>using namespace std;class channal
{
public:channal(int fd, pid_t id, int num): _fd(fd), _id(id), _name("channal-" + to_string(num)){}int _fd;pid_t _id;string _name;
};void download()
{cout << "我是一个下载任务,我的pid是:" << getpid() << endl;
}void printlog()
{cout << "我是一个打印日志任务,我的pid是:" << getpid() << endl;
}void compressedfile()
{cout << "我是一个压缩文件任务,我的pid是:" << getpid() << endl;
}using task_t = function<void()>;class task
{
public:task(){_messions.push_back(download);_messions.push_back(printlog);_messions.push_back(compressedfile);srand((unsigned)time(nullptr));}task_t gettask(int num){assert(num >= 0 && num < _messions.size());return _messions[num];}int gettasksize(){return _messions.size();}string gettaskname(int num){assert(num >= 0 && num < _messions.size());switch (num){case _download:return "_download";break;case _printlog:return "_printlog";break;case _compressedfile:return "_compressedfile";break;default:return "";break;}}private:static const int _download = 0;static const int _printlog = 1;static const int _compressedfile = 2;vector<task_t> _messions;
};task task_list;int main()
{vector<channal> channals;vector<int> tmp;for (int i = 0; i < 5; i++){// 创建与子进程通信的管道(信道)int pipefd[2];int piperet = pipe(pipefd);if (piperet < 0){cout << "erron: " << errno << " error: " << strerror(errno) << endl;return 1;}// 创建子进程,建立信道pid_t id = fork();if (id < 0){cout << "erron: " << errno << " error: " << strerror(errno) << endl;return 1;}else if (id == 0){// 子进程if (!tmp.empty()){for (int i = 0; i < tmp.size(); i++){close(tmp[i]);}}close(pipefd[1]); // 关闭写端while (true){int mession = 0;ssize_t n = read(pipefd[0], &mession, sizeof(mession));if (n < 0){cout << "erron: " << errno << " error: " << strerror(errno) << endl;exit(1);}else if (n != 4)break;else{// 执行任务task_list.gettask(mession)();}}exit(0);}// 父进程close(pipefd[0]); // 关闭读端channals.push_back({pipefd[1], id, i});tmp.push_back(pipefd[1]);}// 父进程就可以开始给子进程分配任务了int pos = 0;int x = 0;while (true){pos %= channals.size();int mession = rand() % task_list.gettasksize();ssize_t n = write(channals[pos]._fd, &mession, sizeof(mession));cout << "分配给信道:" << channals[pos]._name << "任务:" << task_list.gettaskname(mession) << "它的pid是:" << channals[pos]._id << endl;if (n < 0){cout << "erron: " << errno << " error: " << strerror(errno) << endl;return 1;}sleep(1);pos++;x++;if (x == 10){break;}}// // 回收子进程// for (int i = 0; i < channals.size(); i++)// {//     close(channals[i]._fd);// }// for (int i = 0; i < channals.size(); i++)// {//     pid_t retpid = waitpid(channals[i]._id, nullptr, 0);//     if (retpid == channals[i]._id)//     {//         cout << "回收子进程成功:" << retpid << endl;//     }// }// for (int i = channals.size() - 1; i >= 0; i--)// {//     close(channals[i]._fd);//     pid_t retpid = waitpid(channals[i]._id, nullptr, 0);//     if (retpid == channals[i]._id)//     {//         cout << "回收子进程成功:" << retpid << endl;//     }// }for (int i = 0; i < channals.size(); i++){close(channals[i]._fd);pid_t retpid = waitpid(channals[i]._id, nullptr, 0);if (retpid == channals[i]._id){cout << "回收子进程成功:" << retpid << endl;}}return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

专业140+总分410+华南理工大学811信号与系统考研经验华工电子信息与通信,真题,大纲,参考书。

23考研已经落幕&#xff0c;我也成功的上岸华工&#xff0c;回首这一年多的历程&#xff0c;也是有一些经验想和大家分享一下。 首先说一下个人情况&#xff0c;本科211&#xff0c;初试成绩400分。专业课140。 整体时间安排 对于考研&#xff0c;很重要的一环就是时间安排&…

AJAX——URL查询参数

1 URL查询参数 定义&#xff1a;浏览器提供给服务器的额外信息&#xff0c;让服务器返回浏览器想要的数据 语法&#xff1a;http://xxxx.com/xxx/xxx?参数名1值1 & 参数名2值2 2 axios-查询参数 语法&#xff1a;使用axios提供的 params 选项 注意&#xff1a;axios在…

微信小程序的了解和使用

微信小程序 微信小程序的项目组成 pages 文件夹 用于存放所有的小程序页面 logs 文件夹 用于存放所有的日志文件 utils 文件夹 用于存放工具性质的模块 js app.js 小程序的入口文件 app.json 小程序的全局配置文件 app.wxss 全局样式文件 project.config.json 项目配置文…

Docker 有哪些常用的命令和操作?

Docker是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的容器中&#xff0c;然后发布到任何流行的Linux机器或Windows机器上&#xff0c;也可以实现虚拟化。以下是Docker的一些常用命令和操作&#xff1a; 安装和启动Docker 要使用Do…

EMC学习笔记(二十四)降低EMI的PCB设计指南(四)

降低EMI的PCB设计指南&#xff08;四&#xff09; 1.电路板分区2.信号走线2.1 电容和电感串扰2.2 天线2.3 端接和传输线2.4输入端的阻抗匹配 tips&#xff1a;资料主要来自网络&#xff0c;仅供学习使用。 1.电路板分区 电路板分区与电路板平面规划具有相同的基本含义&#x…

RabbitMQ的延迟队列实现[死信队列](笔记一)

关于死信队列的使用场景不再强调&#xff0c;只针对服务端配置 注意&#xff1a; 本文只针对实现死信队列的rabbitMQ基本配置步骤进行阐述和实现 目录 1、docker-compose 安装rabbitMq2、查看对应的版本及插件下载3、安装插件和检测 1、docker-compose 安装rabbitMq a、使用d…

flask+python高校学生综合测评管理系统 phl8b

系统包括管理员、教师和学生三个角色&#xff1b; 。通过研究&#xff0c;以MySQL为后端数据库&#xff0c;以python为前端技术&#xff0c;以pycharm为开发平台&#xff0c;采用vue架构&#xff0c;建立一个提供个人中心、学生管理、教师管理、课程类型管理、课程信息管理、学…

《统计学简易速速上手小册》第6章:多变量数据分析(2024 最新版)

文章目录 6.1 主成分分析&#xff08;PCA&#xff09;6.1.1 基础知识6.1.2 主要案例&#xff1a;客户细分6.1.3 拓展案例 1&#xff1a;面部识别6.1.4 拓展案例 2&#xff1a;基因数据分析 6.2 聚类分析6.2.1 基础知识6.2.2 主要案例&#xff1a;市场细分6.2.3 拓展案例 1&…

【leetcode】965. 单值二叉树

题目链接 965. 单值二叉树 bool isUnivalTree(struct TreeNode* root) {// if (root->left ! NULL && root->right ! NULL) {// return root->val root->left->val// && root->val root->right->val// && isUnivalTr…

python+django人力资源管理系统7w5x3

技术栈 后端&#xff1a;python 前端&#xff1a;vue.jselementui 框架&#xff1a;django Python版本&#xff1a;python3.7 数据库&#xff1a;mysql5.7 数据库工具&#xff1a;Navicat 开发软件&#xff1a;PyCharm .设计框架&#xff1a;Vue 1. 表现层&#xff1a;写多…

深入学习Pandas:数据连接、合并、加入、添加、重构函数的全面指南【第72篇—python:数据连接】

深入学习Pandas&#xff1a;数据连接、合并、加入、添加、重构函数的全面指南 Pandas是Python中最强大且广泛使用的数据处理库之一&#xff0c;提供了丰富的函数和工具&#xff0c;以便更轻松地处理和分析数据。在本文中&#xff0c;我们将深入探讨Pandas中一系列数据连接、合…

离线数仓(一)【数仓概念、需求架构】

前言 今天开始学习数仓的内容&#xff0c;之前花费一年半的时间已经学完了 Hadoop、Hive、Zookeeper、Spark、HBase、Flume、Sqoop、Kafka、Flink 等基础组件。把学过的内容用到实践这是最重要的&#xff0c;相信会有很大的收获。 1、数据仓库概念 1.1、概念 数据仓库&#x…

如何避免陷入穷忙的陷阱

哈喽&#xff0c;你好啊&#xff0c;我是雷工&#xff01; 在2006年小日子过得不错的日本出了一部纪录片《穷忙族》&#xff0c; 记录了一些收入不多却整日奔波劳碌&#xff0c;虽然努力工作&#xff0c;却依然无法摆脱贫穷的一群人。 他们越忙越穷&#xff0c;越穷越忙&#…

C语言之预处理详解

目录 1. 预定义符号2. #define定义常量3. #define定义宏练习 4. 带有副作用的宏参数5. 宏替换的规则6. 宏函数的对比宏和函数的一个对比 7. #和###运算符##运算符 8. 命名约定9. #undef10. 命令行定义11. 条件编译常见的条件编译 12. 头文件的包含头文件的包含方式库文件包含嵌…

3d渲染100农场如何使用?渲染100邀请码1a12

3d渲染农场通常用于电影、动画或视觉效果的渲染&#xff0c;本文以广受好评的渲染100农场为例&#xff0c;来讲解它的使用方法。 1、注册账号 前往渲染100官网(http://www.xuanran100.com/?ycode1a12)注册账号&#xff0c; 新用户注册记得填邀请码1a12&#xff0c;有30元大礼…

Netty应用(五) 之 Netty引入 EventLoop

目录 第三章 Netty 1.什么是Netty&#xff1f; 2.为什么需要使用Netty&#xff1f; 3.Netty的发展历程 4.谁在使用Netty&#xff1f; 5.为什么上述这些分布式产品都使用Netty&#xff1f; 6.第一个Netty应用 7.如何理解Netty是NIO的封装 8.logback日志使用的加强 9.Ev…

腾讯2023年终奖揭秘:最高30个月!

腾讯2023年终奖揭秘&#xff1a;最高30个月&#xff01; 关于腾讯公司2023年度年终奖的消息引起了广泛的关注。据报道&#xff0c;腾讯今年的年终奖金额可达到员工月薪的5个月&#xff0c;这无疑是许多互联网从业者梦寐以求的福利待遇。作为中国互联网行业的巨头之一&#xff…

Leecode之反转链表

一.题目及剖析 https://leetcode.cn/problems/reverse-linked-list/description/ 二.思路引入 设定三个指针,n1指向空, n2指向head,n3指向下一个元素,将n2->next指向n1,然后三个指针向后遍历重复即可 三.代码引入 /*** Definition for singly-linked list.* struct List…

【JavaEE】----SpringBoot的创建和使用

目录 1.Spring与SpringBoot的区别&#xff08;面试&#xff09; 2. SpringBoot的创建 3.SpringBoot创建时的问题及解决 4.SpringBoot的目录学习 5.创建一个SpringBoot 项目并且启动 1.Spring与SpringBoot的区别&#xff08;面试&#xff09; Spring 的诞⽣是为了简化 Java 程…

2024给你一些Android 应用性能优化的建议

2024给你一些Android 应用性能优化的建议 在当今激烈竞争的移动应用市场中&#xff0c;用户对应用性能和体验的要求越来越高。因此&#xff0c;进行 Android 应用性能优化是开发过程中必不可少的一环。下面将详细介绍如何提升应用的性能&#xff0c;以提升用户体验。 1. 优化…