multiprocessing解析(二):Pool解析

前面一篇已经把multiprocessing.Process这个最基础的类给解剖了,接下来的这篇就是整个multiprocessing中最重要的类Pool的浅析了,因为如果把Pool的所有方方面面都顾及到的话篇幅会比较长,所以我只会把Pool的整体框架整理一下,细节的内容可以更多的去阅读源码。
1. multiprocessing.Pool的几个参数的解读
Pool的构造参数中有三个我认为比较有用的,先给大家介绍一下。processes表示的是进程池pool中进程个数,如果没有指定那就是调用multiprocessing.get_cpu()获得cpu个数作为processesinitializer是在初始化pool中的worker的时候调用的初始化函数,例如你每一个worker需要连接数据库,那么你可以在initializer中去做这件事,这样每一个worker的数据库连接都是独立的。maxtasksperchild是指每一个worker最多被重复调用的次数,例如maxtasksperchild = 3则表示pool中的worker最多能处理3次任务,然后就会被销毁,然后再重新初始化一个worker,如果你认为你的worker可能会存在内存泄露的可能那么你可以把maxtasksperchild设置成一个合理的值,这样就避免worker一直存活导致内存一直增长。
2. 核心流程图
multiprocessing.Pool中涉及到三个核心队列和三个核心的线程,如果用文字来描述可能会比较枯燥,我就大致的画了一张这些对象之间的关系图:
这里写图片描述
3. multiprocessing.Pool的初始化
在初始化阶段主要做了如下的几件事:
一、初始化队列,就是上文提到的_taskqueue_outqueue_inqueue,作用不再赘述。
二、调用_repopulate_pool()生成pool中的worker(multiprocessing.Process对象),生成的worker设置了daemon属性,保证在主进程退出的时候pool中的worker也都会退出。
三、依次生成_handle_worker_handle_taks_handle_result三个线程,功能在上文中大致有所了解,后面我会把这三个线程的主要功能在详细讲解。
四、实例化multiprocessing.util.Finalize对象,这个类我们在前面的文章中讲过了,就是在_finalizer_registry这个全局字典中加入一个对象,在_exit_function()中执行。当我们手动执行pool.terminate()或者主进程退出的时候会执行这个清理函数。
4. Pool中的worker源码解析
在pool初始化的时候调用Pool._maintain_pool()->Pool._repopulate_pool()->worker()来生成进程池中的worker,首先是调用initializer()初始化函数来进行一次初始化,接着就是在while循环中有条件的循环接收任务并执行,这里的条件就是如果传入了maxtasks(也即是我们之前提到的Pool的初始化参数maxtasksperchild),那么while循环最多接收、执行maxtasks个任务,然后退出。worker从_inqueue接收任务,执行完之后把结果传入_outqueue。哦,对了,worker在收到一个sentinel任务的时候也会退出,在前面我们注册的Finalize对象中就会给每一个worker发送一个sentinel任务。
5._handle_tasks线程
_taskqueue队列中获取任务,然后再转发给_inqueue,然后worker再从_inqueue获得任务执行。那么是谁把任务发送到_taskqueue队列中的呢?当我们调用apply_async()map_async()的时候就会往_taskqueue队列传入任务。
apply_async()中会生成一个代表运行结果的ApplyResult对象,然后在pool对象中_cache列表保存了每一个任务对应的ApplyResult对象,在后面的_handle_results部分我们会看到就是通过_cache获得对应任务的ApplyResult对象,然后调用ApplyResult._set()来返回任务的执行结果。
如果收到了_handle_workers线程发来的结束sentinel,那么_handle_tasksfor循环就会结束,然后给_handle_results线程、pool中的所有worker都发送结束sentinel。
6. _handle_workers线程
就是保持进程pool中能一直保持固定个数的worker,在上面的worker中我们看到一个worker有可能会退出(当然worker异常也可能会退出),那么在_handle_workers线程就会join它,然后再重新生成一个worker加入pool中。当我们terminate pool(_terminate_pool())的时候会把_handle_workers线程的状态设置成TERMINATE,这样_handle_workers线程while循环退出,然后给上面的_handle_tasks线程发送结束sentinel。
7. _handle_results线程
_outqueue中获取已经结束的任务,然后设置任务对应的ApplyResult的结果。
8. terminate函数
有两种情况我们会调用_terminate_pool(),一个就是我们调用pool.terminate(),另外一个就是主进程运行结束_exit_function()->_finalizer_registry->_terminate_pool()。虽然我们看到最终会调用_terminate_pool()函数,但是其实真正做清理工作的是这一行代码worker_handle._state = TERMINATE,真正触发了一系列的terminate。
这里写图片描述
9. multiprocessing.Pool的主要内容就介绍到这里,还包含一些其它的功能函数,基本能从代码中了解到实现原理。

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页