linux进程池的创建方法
1.为什么要有进程池
如果你了解过STL的底层设计,你会发现在其中会有一个叫做内存池的设计。其作用就是先申请出一片空间,如果后续你需要对你的容器进行扩容,所扩展的空间就从内存池里取的。这样可以提高扩容的效率。
比如你要扩容100次,如果每次扩容都向系统申请空间的话,效率就很低,因为向系统申请空间也是需要时间的,所以内存池的作用就是一次性申请一篇空间,当需要扩容时就向内存池要,可以提高扩容的效率。
既然这样,我们也可以如此理解进程池,一次性创建一批进程,如果有任务要执行就交给进程池中空闲的进程来做,而不是一有任务就创建一个新的进程,进程池的目的也是为了提供效率,节省创建进程的时间消耗。
通过预先创建和复用进程,进程池能够提高任务执行效率,避免频繁创建和销毁进程带来的系统开销。
2.进程池的工作原理
进程池的核心思想是创建固定数量的进程,然后将需要执行的任务分配给这些进程来处理。当某个任务完成后,该进程可以继续处理下一个任务,而不是销毁。这样可以减少频繁创建和销毁进程带来的资源浪费。
2.1 进程池的工作流程
初始化:预处理创建一定数量的进程,形成进程池。
任务分配:当有任务需要处理时,将任务分配给某个空闲进程。
任务处理:空闲进程接受任务并执行。
复用进程:任务执行完成后,进程回到池中,等待新的任务。
退出:当没有新的任务且需要关闭进程池,池中进程将逐个退出。
3. 进程池的实现(重点)
本文将着重讲解进程池的实现步骤。
初始化
通过运行可执行文件时传入的参数来创建一定数量的子进程。
如:创建5个子进程
代码实现:
现在我们来分析下,我们要实现进程池的功能:创建子进程,发送任务给子进程执行,子进程的轮询,杀死进程,等待进程,一些Debug功能。这样的话我们完全可以创建一个类来封装这些功能。除此之外,我们还需要描述一下子进程,为此也需要创建一个描述管道的类。
3.1 Channel类
Channel类的功能主要是来描述管道的,具有的属性有该管道对应的子进程的id,名字,写端描述符。
Channel类的实现比较简单,直接看代码吧:
3.2 ProcessPool类
ProcessPool类的功能主要是来描述进程池的,具有的属性有该管道对应的子进程的数量,所有的管道。
类似于这样:
ProcessPool类的框架:
3.2.1 创建子进程
因为我们需要创建指定数目的进程,用一个循环来写就可以了。在循环中,父进程每次都会创建一个子进程出来,然后用管道于它们链接,注意因为是父进程给子进程分配任务,所以需要把父进程的读端关闭,子进程的写端关闭。
初版:
为了让子进程执行相应的任务,我们还可以添加一个回调函数worker
。worker
函数主要作用是选择要执行的任务,具体的任务,我们还需要自己创建,为此还可以创建3个测试用的任务,用一个函数指针数组去保存这些函数。
代码如下:
第二版:
第二版相对第一版,多了个回调函数,这个回调函数可以让我实现相对应的工作。同时也多个重定向功能,把原本标准输入的功能给到了pipefd[0],也就是说当子进程去读标准输入内的数据时,会去读管道中的数据。
这是一个典型的标准输入重定向操作,将管道的读端作为当前进程的输入来源
其实该代码中还存在bug,有个魔鬼细节存在!!!
第三版:
其实对于子进程来说,它的写端并没有全部关闭。下面我们来画图:
创建第一个管道,这个图如果看过我讲匿名管道的那篇的话,还是比较熟悉的。
现在我们来创建第二个管道,我们知道文件描述符的创建是遵循当前没有直接使用的最小的一个下标,作为新的文件描述符。所以呢,新创建的管道的pipefd[0]
依旧是在先前的位置,可是写端就不是了,原先的写端并没有被关闭,我们新管道创建的pipefd[1]
会在其下方被创建。
然后要知道的是,子进程是由父进程创建的,它的各项数据是由父进程复制而来,也就会把上一个管道的写端给复制过了,但是子进程可是关闭不了它的,因为它只能拿到新创建管道的写端pipefd[1]
的位置。具体情况如图:
所以为了关闭子进程的所有写端,我们需要用有个数组去保存父进程中的写端,然后再子进程中把它们一一关闭。
代码如下:
3.2.2 杀死所有进程
进程池也有不需要的时候,当进程池不需要了,我们就要回收子进程了,怎么回收呢?当然是进程等待了。
杀死子进程也就是等待子进程。
要注意的是别忘了关闭文件描述符
进程等待是必须的,不然的话子进程会变成僵尸进程的。
3.2.3 其他功能
因为这些功能都比较简单就一块讲了吧。
子进程的轮询,我能总不能让一个子进程一直跑任务吧,为了合理利用子进程,我们可以设计也该轮询函数,让子进程的任务分配"雨露均沾"。
发送任务代码:
debug:
3.3 控制进程池
完成上面的功能就需要我们去控制进程池的子进程了。
主要包括创建进程池,控制子进程,回收子进程。
运行结果:
4. 完整代码
5. 总结
进程池的核心思想是创建固定数量的进程,然后将需要执行的任务分配给这些进程来处理。当某个任务完成后,该进程可以继续处理下一个任务,而不是销毁。这样可以减少频繁创建和销毁进程带来的资源浪费。