【Linux】匿名管道的应用场景 --- 进程池

👦个人主页:Weraphael

✍🏻作者简介:目前正在学习c++和算法

✈️专栏:Linux

🐋 希望大家多多支持,咱一起进步!😁

如果文章有啥瑕疵,希望大佬指点一二

如果文章对你有帮助的话

欢迎 评论💬 点赞👍🏻 收藏 📂 加关注😍


前言

本篇博客不会对代码有非常详细的解析,但是只要你看完这篇博客(点击跳转),然后再来看这篇,我保证跟看小说一样 ~

目录

一、池化技术

不知道大家有没有听过xx池(如内存池等),这些池其实统称池化技术

以内存池为例,比方说有一个偏远的村庄,这个村庄离河边有一定距离,那么村民需要水的时候就跑去河边打水,可是每次需要水的时候就去打未免效率太低了。因此,村民可以提前打完一周所需要的用水。

因此,可以将村民看作是程序,而水则是内存。在没有内存池的情况下,程序每次需要内存时都需要向操作系统申请,而频繁进行系统调用是有成本的 ,就像村民每次需要水都要跑去河边打水一样,效率较低。而有了内存池,就像村民提前打好一周的水存放在家里一样,程序在启动时就预先分配了一定量的内存,并将其存放在内存池中。当程序需要内存时,就直接从内存池中获取,而不是每次都向系统请求,这样可以减少内存分配的开销和系统的负担,提高程序的运行效率。

因此,不管是xx池,这些池化技术的共同特点是:通过提前分配一定数量的资源并在需要时复用这些资源,来提高系统的性能和效率。

进程池是一种并发编程中常用的技术,特别是在需要处理大量任务的情况下。它类似于内存池的概念,想象一下有很多任务需要处理,而每个任务都需要独立的进程来执行。如果每次都创建一个新的进程来处理任务(如创建进程控制块、进程地址空间等),会增加系统开销和资源消耗,特别是在任务量大的情况下。这时,就可以使用进程池。

进程池在程序启动时就会创建一定数量的进程,并将它们保存在池中。当有任务需要执行时,就从池中获取一个空闲的进程来处理任务,任务执行完毕后,该进程不会被销毁,而是返回到池中,等待下一个任务的到来。这样可以避免频繁创建和销毁进程的开销,提高系统的性能和效率。

二、设计思路

首先我们可以通过父进程bash,来提前创建好若干个子进程。接下来为了让父进程和这些子进程建立联系,在父进程和子进程之间设计匿名管道,父进程下达任务数据到管道中,再由子进程接收任务并执行。

三、代码实现之管理管道

为了让子进程和父进程建立可靠的数据传输通道,应该要对管道进行管理。那么就要先描述,再组织。

描述管道的字段有:

  • _cmdfd:父进程需要向哪个管道发送任务,因此需要知道各管道的写端,即文件描述符。
  • _sonid:当父进程将任务发送到管道中时,子进程可以通过自己的pid来确定是否是自己需要执行的任务。

四、代码实现之创建子进程和初始化管道字段

我们可以封装一个函数创建子进程,并且初始化子进程对应的管道字段。具体代码如下:

传参的小技巧

  1. 输入型参数:用于传递数据给函数,但形参的改变不用影响实参 -> const&
  2. 输出型参数:形参改变要影响实参,当函数被调用时,输出型参数可以不初始化 -> *
  3. 输入输出型参数:和输出型参数的区别是 -> 这些参数在函数调用前需要被初始化 -> &

我们可以通过打印的方式来来验证是否真的创建成功了

【程序结果】

五、代码实现之任务列表

新建一个文件名为tasks.hpp,里面用来存放任务的实现。具体代码如下:

补充:.hppc++常见的头文件。该文件中通常会包含类的定义、模板类和函数的实现,即定义和声明不分离。

六、代码实现之发布任务

父进程需要发布任务给进程池中的任意一个进程,需要经历以下步骤:

  1. 选择任务。这里我们可以这样规定:我使用一个数据结构(如vector)管理任务列表,然后父进程可以通过随机选取任务列表中的下标,我们可以称为任务码。最后将这个任务码发送给子进程,让子进程来执行任务码对应的任务。
  2. 选择进程:我们可以随机选取进程池中的任意一个进程来执行。
  3. 发布任务:将任务码写入到管道文件中

首先我们需要将任务列表中的所有任务用一种数据结构管理起来,这里就以vector为例

C++标准库中的 <functional> 头文件提供了一组模板类和函数,用于实现函数对象(包括函数指针)的封装、组合和操作

接下来我们需要控制任意一个进程来执行任务

七、代码实现之读取任务

【程序结果】

八、代码实现之菜单版

我们只需要修改【发布任务】的代码即可实现

【程序结果】

九、代码实现之子进程回收

如上菜单所示,当程序退出的时候,可以选择将子进程回收

【程序结果】

十、一个隐藏bug

当我们的父进程创建子进程的时候,因为我们用的是循环的方式,所以导致父进程每创建一个子进程,那么下一个进程就会继承上一个管道的写端。这样子进程之间也可以相互进行通信了。

那有什么问题呢?我们上面代码都跑的好好的呀!但如果你写出以下代码,就是一个bug

【程序结果】

为什么会阻塞呢?

在关闭第一个进程的时候,我们先是将写端关闭,那么对应的读端的read函数就会返回0,表示可以退出通信了。退出通信后代码就要开始回收第一个进程。可是,第二个进程甚至后面的进程都继承了这个写端(写端没关完),那么操作系统还是认定系统还在通信,那么read函数就不会返回0,可是我们在关闭文件描述符后,紧接就要回收这第一个进程了,可是第一个进程还在通信,并没有退出,那么系统就要等待它退出,所以就阻塞了。

  • 解决方法1:先将文件描述符全部关闭,再回收所有子进程。(最开始的方案)

  • 解决方法2:可以关闭和回收一起做。只需要倒着来就行。(可以配合刚开始的那个图理解)

【运行结果】

  • 解决方法3:让一个管道只有一个读端,一个写端。在创建一个子进程前,将继承父进程的写端文件描述符关闭即可。

【程序结果】

十一、相关代码

Gitee仓库链接:点击跳转

相关推荐
恋红尘5 小时前
Mysql
数据库·mysql
Full Stack Developme5 小时前
java.nio 包详解
java·python·nio
SundayBear5 小时前
嵌入式进阶:C语言内联汇编
c语言·开发语言·汇编
paishishaba5 小时前
数据库设计原则
数据库
零千叶5 小时前
【面试】Java JVM 调优面试手册
java·开发语言·jvm
代码充电宝5 小时前
LeetCode 算法题【简单】290. 单词规律
java·算法·leetcode·职场和发展·哈希表
li3714908905 小时前
nginx报400bad request 请求头过大异常处理
java·运维·nginx
摇滚侠5 小时前
Spring Boot 项目, idea 控制台日志设置彩色
java·spring boot·intellij-idea
久曲健的测试窝5 小时前
Jenkins Share Library教程 —— 开发入门
运维·servlet·jenkins
曹牧6 小时前
oracle:NOT IN
数据库·oracle