前面一篇已经把multiprocessing.Process
这个最基础的类给解剖了,接下来的这篇就是整个multiprocessing
中最重要的类Pool
的浅析了,因为如果把Pool
的所有方方面面都顾及到的话篇幅会比较长,所以我只会把Pool
的整体框架整理一下,细节的内容可以更多的去阅读源码。
1. multiprocessing.Pool
的几个参数的解读
Pool
的构造参数中有三个我认为比较有用的,先给大家介绍一下。processes
表示的是进程池pool中进程个数,如果没有指定那就是调用multiprocessing.get_cpu()
获得cpu个数作为processes
。initializer
是在初始化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_tasks
的for
循环就会结束,然后给_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
的主要内容就介绍到这里,还包含一些其它的功能函数,基本能从代码中了解到实现原理。
multiprocessing解析(二):Pool解析
最新推荐文章于 2022-07-26 13:20:18 发布