十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Linux 进程池实战:匿名管道、轮询调度与隐藏的阻塞陷阱

Linux 进程池实战:匿名管道、轮询调度与隐藏的阻塞陷阱 文章目录前言代码展示代码功能Task.hppProcessPool.hpp整体运行思路运行演示隐藏的问题前言本次介绍的是使用匿名管道创建进程池的操作其中会存在一个隐藏的堵塞问题这个在后面会提及。本人所认为的重要部分会以注释的形式展出有一些也会在后面强调代码展示ProcessPool.hpp#ifndef__PROCESS_POOL_HPP__#define__PROCESS_POOL_HPP__#includeTask.hpp#includeunistd.h#includestdlib.h#includeerrno.h#includestring.h#includesys/types.h#includesys/wait.husingnamespacestd;classChannel{public:Channel(intfd,pid_t id):_wfd(fd),_subid(id){// 切记要to_string转换一下格式_namechannel- to_string(_wfd)-to_string(_subid);}intFd(){return_wfd;}pid_tSubid(){return_subid;}stringName(){return_name;}voidSend(intcode){// 发送本质上就是把任务编码通过写端写到管道文件write(_wfd,code,sizeof(code));}voidClose(){// 关闭写端close(_wfd);cout关闭_nameendl;}voidWait(){// 等待本管道对应的子进程退出waitpid(_subid,nullptr,0);cout等待子进程[_name]回收endl;}private:// 每个信道里都包含文件描述符 _wfd// 子进程id _subid// 信道名字 _nameint_wfd;pid_t _subid;string _name;};classChannelManager{public:ChannelManager():_next(0){}voidInsert(intfd,pid_t id){// 向_channels里插入Channel_channels.emplace_back(fd,id);}ChannelSelect(){// 轮询选择信道并返回autoc_channels[_next];_next;_next%_channels.size();returnc;}voidStopSubprocess(){// 通过挨个关闭信道的方式关闭进程池for(autochannel:_channels){channel.Close();}}voidWaitSubprocess(){// 挨个等待每个管道的子进程退出for(autochannel:_channels){channel.Wait();}}private:vectorChannel_channels;//_next用于轮询选择_channels里的Channelint_next;};constintgg10;classProcessPool{public:ProcessPool(intnum):_process_num(num){// 注册任务_tm.Register(Printlog);_tm.Register(Download);_tm.Register(Upload);}boolStart(){// 主要作用是创建管道和父子进程for(inti0;i_process_num;i){// 创建匿名管道intpipefd[2]{0};if(pipe(pipefd)0){perror(pipe);returnfalse;}// 创建子进程intidfork();if(id0){perror(fork);returnfalse;}// 子进程只读不写elseif(id0){close(pipefd[1]);Work(pipefd[0]);close(pipefd[0]);exit(0);}// 父进程只写不读else{close(pipefd[0]);_cm.Insert(pipefd[1],id);}}returntrue;}voidRun(){// 该函数的作用就是选择一个信道生成任务码并将任务码发送至管道里inttaskcode_tm.code();autoc_cm.Select();cout选择一个信道c.Name()endl;c.Send(taskcode);cout发送了一个任务码taskcodeendl;}voidStop(){// 关闭所有父进程的写端即可子进程读端读取不到内容自己就结束了_cm.StopSubprocess();// 回收所有子进程_cm.WaitSubprocess();}private:// Work的主要工作就是在管道里读取任务码并执行相应任务voidWork(intrfd){while(true){intcode;intnumread(rfd,code,sizeof(code));if(num0){perror(read);break;}elseif(num0){if(num!sizeof(code)){continue;}cout子进程[getpid()]收到了一个任务码codeendl;_tm.Execute(code);}else{cout子进程结束endl;break;}}}private://_process_num是要创建的信道数量ChannelManager _cm;TaskManager _tm;int_process_num;};#endifTask.hpp#pragmaonce#includeiostream#includectime#includevectorusingnamespacestd;typedefvoid(*_task)();voidPrintlog(){cout这是一个打印日志的任务endl;}voidUpload(){cout这是一个上传的任务endl;}voidDownload(){cout这是一个下载的任务endl;}classTaskManager{public:TaskManager(){srand(time(nullptr));}voidRegister(_task task){// 在_channels中插入任务_tasks.push_back(task);}intcode(){// 生成随机任务号码returnrand()%_tasks.size();}voidExecute(intcode){// 根据编号执行任务// 先判断编号是否合格再执行if(code0code_tasks.size()){_tasks[code]();}}private:vector_task_tasks;};Main.cc#includeProcessPool.hpp#includecstdiointmain(){ProcessPoolpp(gg);pp.Start();intcnt10;while(cnt--){pp.Run();sleep(1);coutendl;}pp.Stop();return0;}MakefileProcess_Pool:Main.cc ProcessPool.hpp Task.hpp g-o $ $-stdc11.PHONY:clean clean:rm-f Process_Pool代码功能Task.hpp利用函数指针_task调用各种任务函数Printlog()Upload()Download()TaskManager创建名为_tasks存放_task函数指针的vector容器生成随机的、不超过vector容器长度的任务码后面会提到任务码的用处通过任务码执行任务ProcessPool.hppChannel负责记载管道的写端和子进程的id以及信道的名字;把任务码通过写端传给管道;关闭写端;等待子进程退出ChannelManager将一个个的信道通过Insert函数放入名为_channels的verctor容器里;利用成员变量_next进行轮询选择信道;实现每个信道的关闭和等待操作,其本质就是对信道的增删操作ProcessPool因本类相较于前面几个来说较为复杂故此按函数名讲解构造函数运用成员变量_tm里的Register注册任务Start()函数创建匿名管道创建子进程每创建一个管道就要创建一个子进程Run()函数,前面提到的随机任务码在此处被信道通过写端写入了管道内。该函数先选择信道再把任务码发到信道里的管道内Work()函数它其实是子进程的工作函数使子进程能够在读端读取任务码并将其传送给成员变量_tm让它去根据任务码执行相应的任务。有一点要强调一下当read返回值为0的时候意味着父进程的写端关闭也就意味着子进程的任务结束了Stop()函数调用ChannelManager关闭所有父进程的写端并等待子进程退出整体运行思路我们按照main函数的思路来梳理ProcessPool构造_process_num为想要创建的信道数目同时也是子进程的数目用TaskManager里的Register()插入任务在每一次for循环中pipe创建匿名管道创建父子进程父写子读父进程关闭读端然后创建并插入信道子进程关闭写端用读端去读取父进程写在管道里的随机任务码读取完成后关闭子进程读端之后exit子进程退出循环运行Run()cnt的大小就是派发任务的数量此数量可以大于信道的数量。先获取随机任务码再选择一个信道再将其发送给信道调用Stop()关闭所有父进程的写端接着回收刚才exit的子进程运行演示隐藏的问题下一篇再讲
返回列表