0

0

【Linux】匿名管道通信场景——进程池

看不見的法師

看不見的法師

发布时间:2025-06-19 16:58:34

|

254人浏览过

|

来源于php中文网

原创

1. 初始化进程池

  进程池的实现是依靠匿名管道,通过进程间通信使得父进程能够管理多个进程任务,相当于父进程拥有了很多个进程——进程池,通过不同的进程完成指定的任务。   所以我们需要创建多个匿名管道和子进程,进行进程间通信,发送信息给子进程让它们根据接收到的信息处理相关任务。   因为有多个管道和子进程,为了方便父进程使用不同管道发送对应信息给子进程,我们需要将管道的文件描述符以及对应子进程的pid保存起来,我们选择将它们封装在一个channel类中。又因为有多个匿名管道和子进程,所以将多个channel类对象储存在c++stl中的容器vector中来方便父进程进行管理进程池。

代码如下:

代码语言:javascript代码运行次数:0运行复制
int InitProcesspool(int num,std::vector& channels){    for(int i = 0; i < num; i++)//使用循环创建多个匿名管道和子进程    {        //1.创建匿名管道        int pipefd[2] = {0};        int n = pipe(pipefd);        if(n < 0) return 2;//根据不同的返回值判断原因,也可以使用枚举来约定返回值代表的内容        //2.创建子进程        pid_t id = fork();        if(id < 0) return 3;        //3.建立通信管道,父子进程关闭读端或写端        if(id == 0)//子进程        {            //子进程读取,关闭写端            ::close(pipefd[1]);            //dup2            dup2(pipefd[0],0);            //子进程需要执行的内容            Work();            ::exit(0);        }        //父进程        //父进程写入,关闭读端        ::close(pipefd[0]);        channels.emplace_back(pipefd[1],id);//保存在channel对象中并存入vector    }    return 0;}

对于Channel类

代码语言:javascript代码运行次数:0运行复制
class Channel{public:    Channel(int fd,pid_t who):_fd(fd),_who(who)    {        _name = "Channel-"+std::to_string(fd)+"-"+std::to_string(who);    }    std::string GetName()    {        return _name;    }    int GetFd()    {        return _fd;    }    pid_t GetWho()    {        return _who;    }    void Send(int num)//父进程往匿名管道发送信息    {        ::write(_fd,&num,sizeof(num));    }    ~Channel()    {    }private:    int _fd;//保存匿名管道通信的文件描述符    std::string _name;//名字(自己取的)    pid_t _who;//子进程pid};
2. 进程池执行任务2.1 任务管理

  执行任务之前我们需要先确定有哪些任务,怎么执行…所以我们需要进行任务管理,同样我们也是使用一个类TaskManager来进行任务管理:

代码语言:javascript代码运行次数:0运行复制
#include#include#include#includeusing task_t = std::function;//函数指针//不同任务函数    void Load()    {        std::cout<<"正在执行加载任务..."< m;//使用map封装数字与对应的任务函数指针};TaskManager tm;

  选择新创建一个源文件Task.hpp来封装上述内容,上述任务管理类中我们使用map来保存数字与任务函数指针的相关关系,这样通过数字就可以确定是哪一个任务函数;此外选择任务使用的方法是随机数的方法,大家可以根据自己的想法确定不同的方式。

2.2 执行任务发送任务代码语言:javascript代码运行次数:0运行复制
void ExcuteTask(std::vector& channels){    int n = 0;    int count = 10;    while(count--)//执行10次任务    {        //1.选择任务,获取任务编号        int tasknum = tm.SelectTask();        //2.选择子进程,使用轮询选择,派发任务        channels[n++].Send(tasknum);        n%=channels.size();        std::cout<接受并执行任务代码语言:javascript代码运行次数:0运行复制
//子进程接受并执行任务void Work(){    while(true)    {        int num = 0;        int n = ::read(0,&num,sizeof(num));        if(n == sizeof(num))//读取成功            tm.Excute(num);//不要发成n        else if(n == 0)        {            break;        }        else        {            break;        }    }}
3. 清理进程池代码语言:javascript代码运行次数:0运行复制
void CleanProcesspool(std::vector& channels){    for(auto& c : channels)        ::close(c.GetFd());    for(auto& c : channels)    {        pid_t rid = ::waitpid(c.GetWho(),nullptr,0);        if(rid <= 0)        {            std::cout<

  注意这里不能使用一个循环来进行清理,如下面代码是错误的:

代码语言:javascript代码运行次数:0运行复制
void CleanProcesspool(std::vector& channels){    for(auto& c : channels)//只使用一次循环    {    ::close(c.GetFd());        pid_t rid = ::waitpid(c.GetWho(),nullptr,0);        if(rid <= 0)        {            std::cout<

这是因为在创建子进程时,子进程会继承父进程的文件描述符表,因此在第一个匿名管道创建后,例如父进程的4号文件描述符指向该匿名管道写端,那么在创建第二个子进程时,该子进程会继承4号文件描述符也指向第一个匿名管道写端,因此创建的子进程越多,前面匿名管道写端被指向的就越多,所以仅仅关闭一个进程的写端指向,还有其他的写端指向,所以读端无法读到0,也就无法退出,如下图所示:

FreeTTS
FreeTTS

FreeTTS是一个免费开源的在线文本到语音生成解决方案,可以将文本转换成MP3,

下载
【Linux】匿名管道通信场景——进程池
【Linux】匿名管道通信场景——进程池

如果要使用一个循环来清理回收子进程,我们可以从后往前关闭文件描述符,因为最后一个管道写端只有父进程指向。

4. 封装与完整实现

  对于父进程管理进程池我们可以封装一个类来更好的进行管理与实现:

代码语言:javascript代码运行次数:0运行复制
#include #include #include #include #include #include #include #include "Task.hpp"#include "Channel.hpp"// masterclass ProcessPool{public:    int InitProcesspool(int num)    {        for (int i = 0; i < num; i++)        {            // 1.创建匿名管道            int pipefd[2] = {0};            int n = pipe(pipefd);            if (n < 0)                return 2;            // 2.创建子进程            pid_t id = fork();            if (id < 0)                return 3;            // 3.建立通信管道,父子进程关闭读端或写端            if (id == 0) // 子进程            {                // 子进程读取,关闭写端                ::close(pipefd[1]);                // dup2                dup2(pipefd[0], 0);                Work();                ::exit(0);            }            // 父进程            // 父进程写入,关闭读端            ::close(pipefd[0]);            channels.emplace_back(pipefd[1], id);        }        return 0;    }    void ExcuteTask()    {        int n = 0;        int count = 10;        while (count--) // 执行10次任务        {            // 1.选择任务,获取任务编号            int tasknum = tm.SelectTask();            // 2.选择子进程,使用轮询选择,派发任务            channels[n++].Send(tasknum);            n %= channels.size();            std::cout << std::endl;            // std::cout<<"**************************"< channels;};

main函数:

代码语言:javascript代码运行次数:0运行复制
#include "ProcessPool.hpp"int main(int argc, char* argv[]){    //0.获取应该创建管道个数num个    if(argc!=2)    {        std::cout<<"请输入管道个数."< channels;        ProcessPool* pp = new ProcessPool;    //1.初始化进程池——创建进程池    pp->InitProcesspool(num);        //2.执行任务    pp->ExcuteTask();    //3.任务执行完成,回收子进程    pp->CleanProcesspool();    delete pp;        return 0;}

运行结果如下:

【Linux】匿名管道通信场景——进程池
5. 结语

  

相关专题

更多
js获取数组长度的方法
js获取数组长度的方法

在js中,可以利用array对象的length属性来获取数组长度,该属性可设置或返回数组中元素的数目,只需要使用“array.length”语句即可返回表示数组对象的元素个数的数值,也就是长度值。php中文网还提供JavaScript数组的相关下载、相关课程等内容,供大家免费下载使用。

557

2023.06.20

js刷新当前页面
js刷新当前页面

js刷新当前页面的方法:1、reload方法,该方法强迫浏览器刷新当前页面,语法为“location.reload([bForceGet]) ”;2、replace方法,该方法通过指定URL替换当前缓存在历史里(客户端)的项目,因此当使用replace方法之后,不能通过“前进”和“后退”来访问已经被替换的URL,语法为“location.replace(URL) ”。php中文网为大家带来了js刷新当前页面的相关知识、以及相关文章等内容

395

2023.07.04

js四舍五入
js四舍五入

js四舍五入的方法:1、tofixed方法,可把 Number 四舍五入为指定小数位数的数字;2、round() 方法,可把一个数字舍入为最接近的整数。php中文网为大家带来了js四舍五入的相关知识、以及相关文章等内容

756

2023.07.04

js删除节点的方法
js删除节点的方法

js删除节点的方法有:1、removeChild()方法,用于从父节点中移除指定的子节点,它需要两个参数,第一个参数是要删除的子节点,第二个参数是父节点;2、parentNode.removeChild()方法,可以直接通过父节点调用来删除子节点;3、remove()方法,可以直接删除节点,而无需指定父节点;4、innerHTML属性,用于删除节点的内容。

478

2023.09.01

JavaScript转义字符
JavaScript转义字符

JavaScript中的转义字符是反斜杠和引号,可以在字符串中表示特殊字符或改变字符的含义。本专题为大家提供转义字符相关的文章、下载、课程内容,供大家免费下载体验。

474

2023.09.04

js生成随机数的方法
js生成随机数的方法

js生成随机数的方法有:1、使用random函数生成0-1之间的随机数;2、使用random函数和特定范围来生成随机整数;3、使用random函数和round函数生成0-99之间的随机整数;4、使用random函数和其他函数生成更复杂的随机数;5、使用random函数和其他函数生成范围内的随机小数;6、使用random函数和其他函数生成范围内的随机整数或小数。

1051

2023.09.04

如何启用JavaScript
如何启用JavaScript

JavaScript启用方法有内联脚本、内部脚本、外部脚本和异步加载。详细介绍:1、内联脚本是将JavaScript代码直接嵌入到HTML标签中;2、内部脚本是将JavaScript代码放置在HTML文件的`<script>`标签中;3、外部脚本是将JavaScript代码放置在一个独立的文件;4、外部脚本是将JavaScript代码放置在一个独立的文件。

659

2023.09.12

Js中Symbol类详解
Js中Symbol类详解

javascript中的Symbol数据类型是一种基本数据类型,用于表示独一无二的值。Symbol的特点:1、独一无二,每个Symbol值都是唯一的,不会与其他任何值相等;2、不可变性,Symbol值一旦创建,就不能修改或者重新赋值;3、隐藏性,Symbol值不会被隐式转换为其他类型;4、无法枚举,Symbol值作为对象的属性名时,默认是不可枚举的。

554

2023.09.20

菜鸟裹裹入口以及教程汇总
菜鸟裹裹入口以及教程汇总

本专题整合了菜鸟裹裹入口地址及教程分享,阅读专题下面的文章了解更多详细内容。

0

2026.01.22

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Git 教程
Git 教程

共21课时 | 2.9万人学习

Django 教程
Django 教程

共28课时 | 3.4万人学习

MySQL 教程
MySQL 教程

共48课时 | 1.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号