👦个人主页:Weraphael
✍🏻作者简介:目前正在学习c++和算法
✈️专栏:Linux
🐋 希望大家多多支持,咱一起进步!😁
如果文章有啥瑕疵,希望大佬指点一二
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注😍
前言
本篇博客不会对代码有非常详细的解析,但是只要你看完这篇博客(点击跳转),然后再来看这篇,我保证跟看小说一样 ~
目录
一、池化技术
不知道大家有没有听过xx池
(如内存池等),这些池其实统称池化技术。
以内存池为例,比方说有一个偏远的村庄,这个村庄离河边有一定距离,那么村民需要水的时候就跑去河边打水,可是每次需要水的时候就去打未免效率太低了。因此,村民可以提前打完一周所需要的用水。
因此,可以将村民看作是程序,而水则是内存。在没有内存池的情况下,程序每次需要内存时都需要向操作系统申请,而频繁进行系统调用是有成本的,就像村民每次需要水都要跑去河边打水一样,效率较低。而有了内存池,就像村民提前打好一周的水存放在家里一样,程序在启动时就预先分配了一定量的内存,并将其存放在内存池中。当程序需要内存时,就直接从内存池中获取,而不是每次都向系统请求,这样可以减少内存分配的开销和系统的负担,提高程序的运行效率。
因此,不管是xx池
,这些池化技术的共同特点是:通过提前分配一定数量的资源并在需要时复用这些资源,来提高系统的性能和效率。
进程池是一种并发编程中常用的技术,特别是在需要处理大量任务的情况下。它类似于内存池的概念,想象一下有很多任务需要处理,而每个任务都需要独立的进程来执行。如果每次都创建一个新的进程来处理任务(如创建进程控制块、进程地址空间等),会增加系统开销和资源消耗,特别是在任务量大的情况下。这时,就可以使用进程池。
进程池在程序启动时就会创建一定数量的进程,并将它们保存在池中。当有任务需要执行时,就从池中获取一个空闲的进程来处理任务,任务执行完毕后,该进程不会被销毁,而是返回到池中,等待下一个任务的到来。这样可以避免频繁创建和销毁进程的开销,提高系统的性能和效率。
二、设计思路
首先我们可以通过父进程bash
,来提前创建好若干个子进程。接下来为了让父进程和这些子进程建立联系,在父进程和子进程之间设计匿名管道,父进程下达任务数据到管道中,再由子进程接收任务并执行。
三、代码实现之管理管道
为了让子进程和父进程建立可靠的数据传输通道,应该要对管道进行管理。那么就要先描述,再组织。
描述管道的字段有:
_cmdfd
:父进程需要向哪个管道发送任务,因此需要知道各管道的写端,即文件描述符。_sonid
:当父进程将任务发送到管道中时,子进程可以通过自己的pid
来确定是否是自己需要执行的任务。
四、代码实现之创建子进程和初始化管道字段
我们可以封装一个函数创建子进程,并且初始化子进程对应的管道字段。具体代码如下:
传参的小技巧
- 输入型参数:用于传递数据给函数,但形参的改变不用影响实参 ->
const&
- 输出型参数:形参改变要影响实参,当函数被调用时,输出型参数可以不初始化 ->
*
- 输入输出型参数:和输出型参数的区别是 -> 这些参数在函数调用前需要被初始化 ->
&
我们可以通过打印的方式来来验证是否真的创建成功了
【程序结果】
五、代码实现之任务列表
新建一个文件名为tasks.hpp
,里面用来存放任务的实现。具体代码如下:
补充:
.hpp
是c++
常见的头文件。该文件中通常会包含类的定义、模板类和函数的实现,即定义和声明不分离。
六、代码实现之发布任务
父进程需要发布任务给进程池中的任意一个进程,需要经历以下步骤:
- 选择任务。这里我们可以这样规定:我使用一个数据结构(如
vector
)管理任务列表,然后父进程可以通过随机选取任务列表中的下标,我们可以称为任务码。最后将这个任务码发送给子进程,让子进程来执行任务码对应的任务。 - 选择进程:我们可以随机选取进程池中的任意一个进程来执行。
- 发布任务:将任务码写入到管道文件中
首先我们需要将任务列表中的所有任务用一种数据结构管理起来,这里就以vector
为例
C++
标准库中的<functional>
头文件提供了一组模板类和函数,用于实现函数对象(包括函数指针)的封装、组合和操作
接下来我们需要控制任意一个进程来执行任务
七、代码实现之读取任务
【程序结果】
八、代码实现之菜单版
我们只需要修改【发布任务】的代码即可实现
【程序结果】
九、代码实现之子进程回收
如上菜单所示,当程序退出的时候,可以选择将子进程回收
【程序结果】
十、一个隐藏bug
当我们的父进程创建子进程的时候,因为我们用的是循环的方式,所以导致父进程每创建一个子进程,那么下一个进程就会继承上一个管道的写端。这样子进程之间也可以相互进行通信了。
那有什么问题呢?我们上面代码都跑的好好的呀!但如果你写出以下代码,就是一个bug
了
【程序结果】
为什么会阻塞呢?
在关闭第一个进程的时候,我们先是将写端关闭,那么对应的读端的read
函数就会返回0
,表示可以退出通信了。退出通信后代码就要开始回收第一个进程。可是,第二个进程甚至后面的进程都继承了这个写端(写端没关完),那么操作系统还是认定系统还在通信,那么read
函数就不会返回0
,可是我们在关闭文件描述符后,紧接就要回收这第一个进程了,可是第一个进程还在通信,并没有退出,那么系统就要等待它退出,所以就阻塞了。
-
解决方法1:先将文件描述符全部关闭,再回收所有子进程。(最开始的方案)
-
解决方法2:可以关闭和回收一起做。只需要倒着来就行。(可以配合刚开始的那个图理解)
【运行结果】
- 解决方法3:让一个管道只有一个读端,一个写端。在创建一个子进程前,将继承父进程的写端文件描述符关闭即可。
【程序结果】
十一、相关代码
Gitee
仓库链接:点击跳转