进程池第二期:隐藏的堵塞问题——子进程继承写端导致的 waitpid 阻塞

发布时间:2026/11/11 12:12:57

进程池第二期:隐藏的堵塞问题——子进程继承写端导致的 waitpid 阻塞 文章目录前言一、更改后的代码二、代码回顾三、原因分析文件描述符的继承四、解决方案1.倒着关闭2. 子进程关闭所有写端3. 先全关再全等方法对比前言在上一期《Linux 进程池实战匿名管道、轮询调度与隐藏的阻塞陷阱》中我们实现了一个基于匿名管道的进程池并故意留下了一个“隐藏的堵塞问题”。现在是时候解释一下了。这一期我会对代码做出略微修改以此来暴露这个问题。一、更改后的代码因为仅更改了ProcessPool.hpp里面的STOP()、StopSubprocess()和WaitSubprocess()函数故此篇只贴出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){}ChannelSelect(){// 轮询选择信道并返回autoc_channels[_next];_next;_next%_channels.size();returnc;}// void StopSubprocess()// {// // 通过挨个关闭信道的方式关闭这次通信// for (auto channel : _channels)// {// channel.Close();// }// }voidInsert(intfd,pid_t id){// 向_channels里插入Channel_channels.emplace_back(fd,id);}// void WaitSubprocess()// {// // 挨个等待每个子进程// for (auto channel : _channels)// {// channel.Wait();// }// }voidStopAndWaitSubprocess(){for(autochannel:_channels){channel.Close();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);}voidRun(){// 该函数的作用就是选择一个信道生成任务码并将任务码发送至管道里inttaskcode_tm.code();autoc_cm.Select();cout选择一个信道c.Name()endl;c.Send(taskcode);cout发送了一个任务码taskcodeendl;}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;}// void Stop()// {// //关闭所有父进程的写端即可子进程读端读取不到内容自己就结束了// _cm.StopSubprocess();// // 回收所有子进程// _cm.WaitSubprocess();// }voidStop(){_cm.StopAndWaitSubprocess();}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;};#endif二、代码回顾这次把 StopSubprocess 和WaitSubprocess 合并成了 StopAndWaitSubprocessvoidChannelManager::StopAndWaitSubprocess(){for(autochannel:_channels){channel.Close();// 关闭父进程的写端channel.Wait();// 等待对应的子进程退出}}运行程序会发现程序会在Stop()卡住终端输出类似于关闭channel-4-973765三、原因分析文件描述符的继承当父进程调用fork()创建子进程时子进程会获得父进程文件描述符表的完整拷贝。我们知道父进程持有管道的写端父进程在创建新子进程时仍然持有之前所有管道的写端。fork() 会把这些写端全部复制给新子进程导致新的子进程就会持有前面所有管道的写端。子进程只在Work()之前关闭了当前管道的写端而忽略了从父进程继承来的其他管道写端。导致在关闭子进程的时候因为当前管道的写端被后来生成的子进程所持有所以当前管道的写端清除不干净子进程的read()返回值不为0子进程就无法退出在read()处阻塞父进程在Wait()处一直等待最终卡死四、解决方案1.倒着关闭最后一个管道的写端只有父进程持有该管道后面不会再出现新的管道不会再出现有新的子进程持有该管道写端的情况。所以只需要关掉父进程持有的最后一个管道的写端即可。read返回值为0子进程不再在read阻塞父进程也不会一直等待。以此类推所以子进程就会完全关闭代码演示voidStopAndWaitSubprocess(){// 方法一for(inti_channels.size()-1;i0;i--){_channels[i].Close();_channels[i].Wait();}}2. 子进程关闭所有写端// 方法二voidCloseAll(){for(autochannel:_channels){channel.Close();}}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){// 方法二每次生成新的子进程后关闭所有写端_cm.CloseAll();close(pipefd[1]);Work(pipefd[0]);close(pipefd[0]);exit(0);}// 父进程只写不读else{close(pipefd[0]);_cm.Insert(pipefd[1],id);}}returntrue;}在子进程 fork 成功后除了关闭当前管道的写端还要关闭它从父进程继承来的所有旧管道写端。这样之后再关闭信道的时候只需要关闭父进程持有的管道写端就可以使read返回0从而使得子进程自动退出父进程成功等到子进程信道关闭成功。3. 先全关再全等voidStop(){// 方法三最后关闭所有写端再结束、等待子进程_cm.CloseAll();_cm.StopAndWaitSubprocess();}先关闭父进程持有的全部写端跟方法一类似此时最后一个管道的写端已经全部关闭故最后一个信道的read()返回值为0子进程退出父进程成功等待最后一个信道删除1。相同的逻辑依次往前遍历即可关闭所有信道。方法对比方案做法是否工作说明正序关一个等一个Close(i); Wait(i)❌ 卡死旧写端没清干净倒序关一个等一个for in-1…0: Close(i); Wait(i)✅每次关的都是最后一个持有者先全关再全等CloseAll(); WaitAll()✅依赖级联退出Wait 会阻塞较久子进程主动关旧写端 先全关再全等CloseAll(); WaitAll()✅最优 子进程独立退出顺序无关代码里并没有真正“删除”信道。_channels 里的 Channel 对象一直存在。故可以把信道删除理解为信道对应的子进程退出该信道生命周期结束↩︎
延伸阅读

更多相关文章

2026/11/11 12:42:11

# 铜价新高,谁在定价?

CSDN版 | 发布日期:2026-09-11 | 母稿直发合规微调 | 标题宽度:10 ≤20 ✅摘要:9月8日,LME铜盘中触及14779美元/吨,连续刷新历史纪录;同一天,中国海关公布前8…

2026/11/11 17:10:19

智能手表晶振:可穿戴设备的隐形时序指挥官

1. 为什么一块智能手表里,晶振不是“配角”,而是隐形指挥官? 你拆过智能手表吗?不是看宣传页上那些光鲜的UI动效、心率曲线或者续航数字,而是真刀真枪拧开后盖、拨开排线、把PCB板翻过来——在密密麻麻的0201封装电阻电…

2026/11/11 17:55:56

多少v算高压电工证多少钱 河南省电工证报考门槛低不低

多少v算高压电工证多少钱 河南省电工证报考门槛低不低 你是不是学历不高,担心自己报不了名?其实高压电工证报考条件并不苛刻,关键是要了解清楚多少v算高压电工证,以及整个流程要花多少钱。别再观望了,早办早安心。…

2026/11/11 17:55:56

长沙县电工证复审千万别踩坑,这些流程和坑你必须知道

长沙县电工证复审千万别踩坑,这些流程和坑你必须知道 你是不是也遇到过这种情况:电工证快要到期了,不知道该去哪复审,担心被中介骗钱?说实话,这种情况太常见了,很多电工朋友都因为信息不对称,被“黑中介”坑了一笔,甚至耽误了工作。今天我就以长沙县…

2026/11/11 17:50:56

安化电工证在那考多久拿证?证书过期了不知道怎么复审延期怎么办

安化电工证在那考多久拿证?证书过期了不知道怎么复审延期怎么办 你是不是也遇到过这种情况:电工证到期了,却不知道怎么复审,更别提找地方重新考?特别是如果你在湖南安化或者周边区域从事电力相关工作,电工证不仅是工作的门槛,更是你职业发展的“硬通货…

2026/11/11 12:42:59

电工证丢失还能补吗?千万别踩坑,丢了证等于失业

电工证丢失还能补吗?千万别踩坑,丢了证等于失业 你正在找工作,但手里的电工证突然丢了,这下怎么办?别急,先别慌, 电工证丢失还能补吗 ?答案是肯定的,但 千万别踩坑 ,补办流程如果不了解,可能会耽误你上岗、甚至影响到你的收入。…

2026/11/11 0:02:26

考低压电工证通过率考试真题与备考技巧

低压电工证通过率高不高?学历不高也能考,值不值得考? 你是不是总听说电工证能帮你找高薪工作,但又担心自己学历不够,考不上?其实,低压电工证考试门槛并没有你想象中那么高,关键是方法对不对。这篇文章就告诉你, 考低压电工证通过率到底有多高 ,…

2026/11/11 0:02:26

低压电工证学时千万别踩坑,濮阳毕业生怎么避坑报名

低压电工证学时千万别踩坑,濮阳毕业生怎么避坑报名 你是不是也和我一样,刚毕业就听说低压电工证学时必须达标才能上岗?可一打听,发现报名机构五花八门,价格参差不齐, 不知道去哪报名怕被中介坑…

2026/11/10 20:08:15

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/10 10:29:37

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑