Multiprocessing Pool内存占用问题及maxtasksperchild配置咨询
maxtasksperchild的疑问 场景背景
我在112核的HPC节点上部署了脚本,启动112个进程处理400个任务(node_combinations是包含400个元组的列表),核心代码如下:
# Parallel Path Probability Calculation # ===================================== node_combinations = [(i, j) for i in g.nodes for j in g.nodes] pool = Pool() start = datetime.datetime.now() logging.info("Start time: %s", start) print("Start time: ", start) pool.starmap(g._print_probability_path_ij, node_combinations) end = datetime.datetime.now() print("End time: ", end) print("Run time: ", end - start) logging.info("End time: %s", end) logging.info("Total run time: %s", start) pool.close() pool.join()
通过htop监控发现:初始112核满负载运行,后续因部分任务耗时短,仅剩少量核满负载,最终所有进程进入睡眠状态。推测是约20个耗时较长的任务占用大量内存,内存不足时进程睡眠且未释放内存导致无法恢复。
技术问题
- 单个进程完成后,其占用的内存资源是否会释放,还是需等待所有进程结束才释放?即当仅剩20个核在运行时,这些进程能否使用所有可用内存,还是只能使用未被已完成进程占用的内存?
- 了解到
maxtasksperchild可能解决该问题,它的作用机制是什么?如何确定每个子进程的合适任务数?
问题解答
1. 单个进程完成后的内存释放逻辑
首先明确:单个子进程完成任务后,只要它没有被重新分配新任务,系统会自动回收它占用的内存——但这里有个关键前提:你的Pool默认是长期存活的子进程池(因为maxtasksperchild=None)。
在你当前的代码里,Pool启动了112个worker进程,这些进程会一直存活到pool.close()和pool.join()执行完毕。当一个worker完成一个任务后,它不会退出,而是会从任务队列里取下一个任务继续执行。这时候,这个worker之前占用的内存如果没有被主动释放(比如代码里的全局变量、大对象没有被清理),就会被保留下来,不会还给系统。
所以你观察到的现象:当大部分短任务完成后,剩下的20个长任务对应的worker进程,其实是在复用之前的worker进程(那些处理过短任务的)。如果那些短任务的worker进程之前占用了内存却没释放,那么这些内存会被"占着",导致长任务的进程无法使用全部可用内存。
简单总结:
- 如果worker进程一直在复用(默认行为),它的内存不会在单个任务完成后释放,直到整个pool关闭。
- 只有当worker进程退出时,系统才会强制回收它的所有内存。
2. maxtasksperchild的作用机制与参数选择
作用机制
maxtasksperchild的核心逻辑很直接:每个worker进程完成指定数量的任务后,就会主动退出,然后由Pool重新启动一个全新的worker进程来接替它。
官方文档里的说明已经点出了核心:这是为了释放worker进程中未被主动回收的资源(比如内存里的大对象、泄漏的内存)。默认值None意味着worker会一直存活到Pool关闭,而设置一个数值后,worker会在完成maxtasksperchild个任务后自我销毁,新的worker是"干净"的,没有之前任务遗留的内存占用。
对你的场景来说,设置这个参数后,那些处理过短任务的worker不会一直占着内存,完成指定任务数后就退出,内存被系统回收,后续的长任务可以使用这些释放出来的内存。
如何确定合适的任务数?
这个没有绝对的标准答案,需要结合你的任务特性来调整:
- 如果你的短任务内存占用小,长任务内存占用大:可以把
maxtasksperchild设为1——即每个worker只处理一个任务就退出。这样每个任务完成后,对应的worker就释放内存,不会有遗留。缺点是会有一定的进程启动/销毁开销,但HPC节点的CPU资源充足,这个开销通常可以忽略。 - 如果短任务很多,想平衡开销和内存释放:可以计算总任务数和worker数量的比例,比如400个任务/112个worker≈3.5,你可以设为3或4。这样每个worker处理3-4个任务后退出,既不会频繁创建进程,又能定期释放内存。
- 测试调优:可以先小范围测试,比如设为1、2、5,观察
htop里的内存占用和任务完成时间,找一个内存占用稳定且耗时没有明显增加的数值。
举个代码修改的例子,把Pool初始化改成:
pool = Pool(maxtasksperchild=1)
这样每个worker处理完一个任务就退出,内存会被系统及时回收。
内容的提问来源于stack exchange,提问作者YamiOmar88

