Linux系统基础-进程间通信(4)_模拟实现进程池

个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创

Linux系统基础-进程间通信(4)_模拟实现进程池

收录于专栏[Linux学习]
本专栏旨在分享学习Linux的一点学习笔记,欢迎大家在评论区交流讨论💌

目录

1. 基础库引入模块

2. Channel类模块

成员变量

构造函数

成员函数

析构函数

总体分析

3. 进程与管道创建模块

参数

函数逻辑

创建管道:

创建子进程:

父进程操作:

总体分析

4. 任务分配与执行控制模块

NextChannel 函数

SendTaskCommand 函数

ctrlProcessOnce 函数

ctrlProcess 函数

5. 清理与资源回收模块

6. 主函数模块

7. 任务管理模块

8. 效果展示


1. 基础库引入模块

功能 : 包含标准库和自定义任务头文件.

#include <iostream>      // 标准输入输出流
#include <string>       // 字符串处理
#include <vector>       // 向量(动态数组)
#include <unistd.h>     // UNIX 标准函数
#include <sys/types.h>  // 数据类型定义
#include <sys/wait.h>   // 进程等待
#include "Task.hpp"     // 自定义任务头文件,假设其中定义了任务相关的函数和类型

 这里引入必要的标准库和自定义库, 以支持后续功能的实现

2. Channel类模块

功能 : 封装管道及进程信息

class Channel
{
public:Channel(int wfd, pid_t id, const std::string& name):_wfd(wfd), _subprocessid(id), _name(name){}int GetWfd() {return _wfd;}pid_t GetProcessId() {return _subprocessid;}std::string GetName() {return _name;}void closeChannel(){close(_wfd);}void Wait(){pid_t rid = waitpid(_subprocessid, nullptr, 0);if(rid > 0){std::cout << "wait " << rid << "success" << std::endl;}}~Channel(){}private:int _wfd;pid_t _subprocessid;std::string _name;
};

成员变量

int _wfd : 代表写入文件描述符, 用于进程间通信

pid_t _subprocessid : 存储子进程的pid, 用于管理子进程

_name : 存储通道的名称

构造函数

构造函数初始化成员变量, 使用初始化列表初始化 _wfd, _subprocessid, _name

成员函数

int GetWfd() : 

返回写入文件描述符, 方便其他类或函数使用这个文件描述符进行写操作

pid_t GetProcessid() : 

返回子进程pid

std::string GetName() :

返回通道名称

void closeChannel() :

关闭写入文件描述符, 释放系统资源, 函数中使用 close(_wd) 确保在不需要时清理资源

void Wait() : 

等待进程结束, 使用waitpid 函数来阻塞当前进程, 直到指定进程结束, 如果waitpid返回值大于0, 表示成功等待子进程, 控制台会输出成功信息.

析构函数

由于文件描述符在 closeChannel 中关闭, 析构时不需要额外操作.

总体分析

这个Channel类为管理进程间提供了一个简单而有效的封装, 它使得管道的创建和管理变得更为方便, 提供了很好的接口来处理与子进程的交互

3. 进程与管道创建模块

功能 : 创建管道和子进程的功能

//进程与管道创建模块
void CreateChannelAndSub(int num, std::vector<Channel> *channels, task_t task)
{for(int i = 0; i < num; i++){//1. 创建管道int pipefd[2] = {0};int n = pipe(pipefd);if(n < 0) exit(1);//2. 创建子进程pid_t id = fork();if(id == 0){if(!channels->empty()){//第二次之后, 开始创建新的管道for(auto &channel : *channels) channel.CloseChannel();}//child - readclose(pipefd[1]);dup2(pipefd[0], 0); //将管道的读端重定向到标准输入task();close(pipefd[0]);exit(0);}//3. 构建一个channel名称std::string channel_name = "Channel-" + std::to_string(i);//父进程close(pipefd[0]);//a. 子进程的pid b. 父进程关心的管道的w端channels->push_back(Channel(pipefd[1], id, channel_name)); }
}

参数

int num:要创建的子进程数量。

std::vector<Channel> *channels:

指向 Channel 对象的指针,用于存储每个子进程及其对应的管道信息。

task_t task:传入的任务函数指针,子进程执行的任务。

函数逻辑

创建管道:

使用 pipe(pipefd) 创建一个管道,pipefd[0] 为读端,pipefd[1] 为写端。若创建失败,调用 exit(1) 终止程序。

创建子进程:

使用 fork() 创建子进程。返回值 id:

如果 id 为 0,表示当前进程为子进程。

子进程执行以下操作:

        如果 channels 不为空,关闭已存在的管道的写端,确保没有文件描述符泄露。

        关闭管道的写端 (close(pipefd[1])),并使用 dup2(pipefd[0], 0) 将管道的读端重定向到标准输入,以便子进程可以从管道读取数据。

        执行传入的 task() 函数。

        关闭读端并退出。

父进程操作:

父进程关闭管道的读端 (close(pipefd[0])),因为只关心写端。

创建一个通道名称,如 "Channel-0",然后将子进程的 PID 和写端的文件描述符封装到 Channel 对象中,并将其添加到 channels 中。

总体分析

这个函数高效地管理了多进程创建与通信。它确保每个子进程拥有独立的管道,并且在创建新的管道前关闭不再使用的管道,避免资源泄露。task 函数的执行允许用户定义子进程的具体工作,实现灵活的任务分配。这种结构适合需要并行处理的场景,如数据处理或并发计算。

4. 任务分配与执行控制模块

功能 : 负责任务的选择和分配给子进程

//0 1 2 3 4 channelnum
int NextChannel(int channelnum)
{static int next = 0;int channel = next;next++;next %= channelnum;return channel;
}//任务分配与执行控制模块
void SendTaskCommand(Channel &Channel, int taskcommand)
{write(Channel.GetWfd(), &taskcommand, sizeof(taskcommand));
}void ctrlProcessOnce(std::vector<Channel> &Channels)
{sleep(1);//1. 选择一个任务int taskcommand = SelectTask();//2. 选择一个信道和进程int channel_index = NextChannel(Channels.size());//3. 发送任务SendTaskCommand(Channels[channel_index], taskcommand);std::cout << std::endl;std::cout << "taskcommand: " << taskcommand << "channel: " << Channels[channel_index].GetName() << "sub process: " << Channels[channel_index].GetProcessId() << std::endl;
}void ctrlProcess(std::vector<Channel> &channels, int times = -1)
{if(times > 0){while(times--){ctrlProcessOnce(channels);}}else{while(true){ctrlProcessOnce(channels);}}
} 

NextChannel 函数

功能:循环返回下一个信道的索引。

实现:

使用静态变量 next 来保存当前的信道索引。

每次调用时返回当前的 next 值,并将其增加,随后使用取模操作确保它不会超过 channelnum - 1。

特点:保证信道的选择是循环的,这样可以平均分配任务到各个信道上。

SendTaskCommand 函数

功能:发送任务命令到指定的信道。

实现:

使用 write 函数将 taskcommand 的值写入信道的写端。

Channel.GetWfd() 获取信道的写文件描述符(WFD),确保命令能够被子进程接收。

特点:该函数直接进行系统调用以发送数据.

ctrlProcessOnce 函数

功能:控制单次任务分配和发送。

实现:

首先调用 sleep(1) 暂停执行,以控制任务发送的频率。

使用 SelectTask() 选择一个任务命令。

调用 NextChannel 获取下一个信道的索引。

发送选定的任务到对应信道。

打印发送的任务信息,包括任务命令、信道名称和子进程 ID。

ctrlProcess 函数

功能:控制多个任务的发送。

实现:

接受一个可选的参数 times,用于指定任务发送的次数。

如果 times 大于 0,则执行 times 次任务发送;否则,进入无限循环,不断发送任务。

特点:通过这个函数,用户可以灵活控制任务的发送次数,适应不同的需求场景。

5. 清理与资源回收模块

功能 : 负责关闭管道和回收子进程

void CleanUpChannel(std::vector<Channel> &channels)
{for(auto &channels : channels){channels.CloseChannel();channels.Wait();}
}

Wait() 会调用操作系统的 wait 系统调用,确保主进程在子进程结束之前不会继续执行,从而避免僵尸进程。 

6. 主函数模块

功能 : 处理命令行参数和程序的主逻辑。

//主函数模块
//处理命令行参数和程序的主逻辑。int main(int argc, char *argv[])
{if (argc != 2){std::cerr << "Usage: " << argv[0] << " processnum" << std::endl;return 1;}int num = std::stoi(argv[1]);LoadTask();std::vector<Channel> channels;// 1. 创建信道和子进程CreateChannelAndSub(num, &channels, work1);// 2. 通过channel控制子进程ctrlProcess(channels, 5);// 3. 回收管道和子进程. a. 关闭所有的写端 b. 回收子进程CleanUpChannel(channels);// sleep(100);return 0;
}

int main(int argc, char *argv[]): 这是程序的入口点。argc 表示命令行参数的数量,argv 是一个字符串数组,存储所有参数。

if (argc != 2): 这个条件检查传入的参数数量是否等于 2。第一个参数(argv[0])是程序本身的名称,第二个参数(argv[1])应该是用户提供的。

7. 任务管理模块

功能 : 主要用于创建, 加载, 和执行一些简单的任务 

#pragma once#include <iostream>
#include <ctime>
#include <cstdlib>
#include <sys/types.h>
#include <unistd.h>#define TaskNum 3typedef void (*task_t)(); // task_t 函数指针类型void Print()
{std::cout << "I am print task" << std::endl;
}
void DownLoad()
{std::cout << "I am a download task" << std::endl;
}
void Flush()
{std::cout << "I am a flush task" << std::endl;
}task_t tasks[TaskNum];void LoadTask()
{srand(time(nullptr) ^ getpid() ^ 17777);tasks[0] = Print;tasks[1] = DownLoad;tasks[2] = Flush;
}void ExcuteTask(int number)
{if (number < 0 || number > 2)return;tasks[number]();
}int SelectTask()
{return rand() % TaskNum;
}void work()
{while (true){int command = 0;int n = read(0, &command, sizeof(command));if (n == sizeof(int)){std::cout << "pid is : " << getpid() << " handler task" << std::endl;ExcuteTask(command);}else if (n == 0){std::cout << "sub process : " << getpid() << " quit" << std::endl;break;}}
}void work1()
{while (true){int command = 0;int n = read(0, &command, sizeof(command));if (n == sizeof(int)){std::cout << "pid is : " << getpid() << " handler task" << std::endl;ExcuteTask(command);}else if (n == 0){std::cout << "sub process : " << getpid() << " quit" << std::endl;break;}}
}void work2()
{while (true){int command = 0;int n = read(0, &command, sizeof(command));if (n == sizeof(int)){std::cout << "pid is : " << getpid() << " handler task" << std::endl;ExcuteTask(command);}else if (n == 0){std::cout << "sub process : " << getpid() << " quit" << std::endl;break;}}
}

typedef void (*task_t)();:

这行代码的意思是:task_t 是一个新的类型别名,它代表一个指向返回类型为 void,且不接受任何参数的函数的指针。

8. 效果展示

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

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

相关文章

Claude 3.5 Sonnent(new)发布,编程能力反超o1

目录 1、近期OpenAI的重磅更新2、Claude 3.5深夜迎来重磅升级3、为什么这么大的更新却连模型版本号都不改一下&#xff1f;4、升级后的Claude 3.5 Sonnet&#xff1a;不只是“更快更强”5、Claude 3.5 Sonnet&#xff08;new&#xff09;适配更多场景&#xff08;1&#xff09;…

[实时计算flink]作业开发上线流程及规范

随着数据量的爆炸性增长和业务需求的日益复杂化&#xff0c;企业对实时数据处理能力的需求愈发迫切。Flink作为一种强大的流处理框架已经成为实时计算标准&#xff0c;其规范化的开发和运维流程对于企业提升数据处理效率、确保系统稳定性至关重要&#xff0c;旨在提升研发效率&…

力扣困难题汇总(16道)

题4&#xff08;困难&#xff09;&#xff1a; 思路&#xff1a; 找两数组中位数&#xff0c;这个看起来简单&#xff0c;顺手反应就是数第(mn)/2个&#xff0c;这个难在要求时间复杂度为log(mn)&#xff0c;所以不能这样搞&#xff0c;我的思路是&#xff1a;每次切割长度为较…

pdf怎么合并在一起?pdf合并的简单方法

pdf怎么合并在一起&#xff1f;在现代办公和学习环境中&#xff0c;PDF&#xff08;便携式文档格式&#xff09;文件因其兼容性强、易于分享和保持格式稳定而广泛应用。然而&#xff0c;在日常工作中&#xff0c;我们经常会遇到需要处理多个PDF文件的情况&#xff0c;例如&…

【uniapp】实现触底加载数据

前言&#xff1a;实现界面触底数据加载。后端接口得支持翻页传参&#xff08;本案例使用django&#xff09; 1、后端接口 1.1 封装翻页公共方法standardPagination.py # -*- coding: utf-8 -*- # Time : 2024/10/15 13:15 # Author : super # File : standardPaginat…

[Hbase]一 HBase基础

1. HBase简介 1.1 HBase定义 HBase数据模型的关键在于 稀疏、分布式、多维、排序 的映射。其中映射 map指代非关系型数据库的 key-Value结构。 1.2 HBase数据模型 1)Name Space 命名空间,类似于关系型数据库的database 概念,每个命名空间下有多个表。HBase 两个自…

MFC工控项目实例二十五多媒体定时计时器

承接专栏《MFC工控项目实例二十四模拟量校正值输入》 用多媒体定时器实现0.1秒计时器 1、在SEAL_PRESSUREDlg.h文件中添加代码 #include<MMSystem.h> #pragma comment(lib,"winmm.lib")class CSEAL_PRESSUREDlg : public CDialog { public:CSEAL_PRESSUREDlg(…

Redis实现全局ID生成器

全局ID生成器 为什么要用全局ID生成器 1.当我们使用数据库自增来实现id的生成时,规律过于明显,会给用户暴露很多信息 2.当我们订单量过大时无法用数据库的一张表来存放订单,如果两张表的id都是自增的话,id就会出现重复 什么是全局ID生成器 全局ID生成器,是一种在分布式系统…

css刮刮卡效果(附源码!!!)

这个刮刮卡PC端和移动端都可以用使用 首发的公众号[小白讲前端]欢迎大家关注浏览 PC端展现 移动端展示 源码(PC和移动端直接复制运行) <!DOCTYPE html> <html><head><meta charset"utf-8"><meta name"viewport" content&quo…

宣恩文旅微短剧双作开机,融合创新助力城市经济发展

近日&#xff0c;宣恩文旅微短剧《弥彰》与《新年恋爱申请&#xff0c;请通过》正式开机。这两部作品由常斌、徐子琁、常喆宽、李果、况琪儿、梅凯杰、刘书赫等实力派演员领衔主演&#xff0c;不仅汇聚了众多演艺界的佼佼者&#xff0c;更承载着宣恩县文化旅游事业的创新与发展…

【从零开始的LeetCode-算法】3075. 幸福值最大化的选择方案

给你一个长度为 n 的数组 happiness &#xff0c;以及一个 正整数 k 。 n 个孩子站成一队&#xff0c;其中第 i 个孩子的 幸福值 是 happiness[i] 。你计划组织 k 轮筛选从这 n 个孩子中选出 k 个孩子。 在每一轮选择一个孩子时&#xff0c;所有 尚未 被选中的孩子的 幸福值 …

【ELK】初始阶段

一、logstash学习 安装的时候最好不要有中文的安装路径 使用相对路径 在 Windows PowerShell 中&#xff0c;如果 logstash 可执行文件位于当前目录下&#xff0c;你需要使用相对路径来运行它。尝试输入以下命令&#xff1a; .\logstash -e ‘input { stdin { } } output { s…

Ubuntu22.04 制作系统ISO镜像

第一步&#xff1a;安装软件-Systemback 1.如果已经添加过ppa&#xff0c;可以删除重新添加或者跳过此步 sudo add-apt-repository --remove ppa:nemh/systemback 2.添加ppa 我是ubuntu20&#xff0c;但这个软件最后支持的是 ubuntu16.04版本&#xff0c;所以加一个16版本…

通过Python爬虫获取商品销量数据,轻松掌握市场动态

为什么选择Python爬虫&#xff1f; 简洁易用&#xff1a;Python语言具有简洁的语法和丰富的库&#xff0c;使得编写爬虫变得简单高效。强大的库支持&#xff1a;Python拥有强大的爬虫框架&#xff08;如Scrapy、BeautifulSoup、Requests等&#xff09;&#xff0c;可以快速实现…

算法1—八大常用排序算法(上)

1.直接插入排序 原理&#xff1a;从arr[0]开始&#xff0c;每次和后一个数据比大小&#xff0c;然后根据需要的是升序还是降序进行操作。 最差的情况下时间复杂度&#xff1a;O&#xff08;n&#xff09; 最好的情况下时间复杂度&#xff1a;O&#xff08;1&#xff09; 所…

漏洞挖掘 | 通过域混淆绕过实现账户接管

由于这是一个私有项目&#xff0c;我将使用 example.com 来代替。 很长一段时间以来&#xff0c;我一直想在漏洞赏金项目中找到一个账户接管&#xff08;ATO&#xff09;漏洞。于是&#xff0c;我开始探索项目范围内的 account.example.com。 我做的第一件事就是注册一个新账…

WebRTC音频 03 - 实时通信框架

WebRTC音频01 - 设备管理 WebRTC音频 02 - Windows平台设备管理 WebRTC音频 03 - 实时通信框架(本文) WebRTC音频 04 - 关键类 WebRTC音频 05 - 音频采集编码 一、前言&#xff1a; 前面介绍了音频设备管理&#xff0c;并且以windows平台为例子&#xff0c;介绍了ADM相关的类…

探索 Web Audio API 的奇妙世界

Web Audio API 是一项强大而灵活的 JavaScript API&#xff0c;它允许开发者在网页中处理和生成音频。本文将带您深入了解 Web Audio API 的基本概念&#xff0c;并介绍一些令人兴奋的应用场景。 1. 什么是 Web Audio API&#xff1f; Web Audio API 是一组用于处理和生成音频…

react18中在列表项中如何使用useRef来获取每项的dom对象

在react中获取dom节点都知道用ref&#xff0c;但是在一个列表循环中&#xff0c;这样做是行不通的&#xff0c;需要做进一步的数据处理。 实现效果 需求&#xff1a;点击每张图片&#xff0c;当前图片出现在可视区域。 代码实现 .box{border: 1px solid #000;list-style: …

计算机专业大学四年的学习路线(非常详细),零基础入门到精通,看这一篇就够了

前言 许多学子选择踏上计算机这条充满挑战与机遇的道路。但在大学四年中&#xff0c;如何规划自己的学习路线&#xff0c;才能在毕业时脱颖而出&#xff0c;成为行业的佼佼者呢&#xff1f; 第一学年&#xff1a;基础知识的奠基 1.1 课程安排 在大学的第一年&#xff0c;重…