Python ThreadPoolExecutor内存泄漏问题排查与多线程内存优化咨询
代码存在的问题
- 核心错误是遍历
future_list时直接删除元素:Python的列表迭代过程中调用remove()会导致迭代器索引错位,大量已完成的future会被跳过而始终保留在列表中,相关的任务上下文、返回值等资源永远无法被回收,这是内存持续上涨的根本原因。 - 冗余的future存储逻辑:你已经为每个future绑定了
done_callback,不需要额外维护全局的future_list来存储所有任务,多余的引用反而会阻碍垃圾回收。 - 不安全的私有属性操作:
executor._threads.clear()、futures.thread._threads_queues.clear()属于直接操作模块私有属性,既没有兼容性保证,也可能引发线程安全问题,ThreadPoolExecutor的上下文管理器会自动处理异常退出时的资源回收,不需要手动干预。 - 手动del变量无意义:Python靠引用计数管理内存,只要对象没有被活跃引用就会被自动回收,你在回调函数末尾手动del变量的操作完全多余,只要future本身还被
future_list引用,内部的所有关联资源都不会释放。
优化后的实现方案
你可以直接移除future_list相关逻辑,靠回调自动处理任务结果即可,线程池本身会复用固定数量的工作线程,不会无限制创建资源:
from concurrent import futures from random import seed, randint import time import psutil import os import threading PROCESS = psutil.Process(os.getpid()) def get_mem_usage(): return PROCESS.memory_info().rss // 1024 def check_for_even(my_number: int) -> int | None: thread_name = threading.current_thread().name print(f"{thread_name} - Checking for even: {my_number}") return my_number if is_even(my_number) else None def get_number() -> int: return randint(1, 10) def is_even(random_number: int) -> bool: return random_number % 2 == 0 def got_number(future: futures.Future): thread_name = threading.current_thread().name # 这里的大列表x会在回调执行完后没有引用,自动被回收 x = [n for n in range(int(1e6))] result = future.result() if result: print(f"{thread_name} - Got even number: {result}") else: print(f"{thread_name} - Number was not even") def main(): seed(int(time.time())) MAX_WORKERS = 5 with futures.ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor: while True: try: print(f"Memory: {get_mem_usage()} KB") print(f"Active thread count: {threading.active_count()}") number = get_number() future = executor.submit(check_for_even, number) future.add_done_callback(got_number) # 简单限流,避免任务提交速度远大于处理速度导致堆积 while executor._work_queue.qsize() > MAX_WORKERS * 2: time.sleep(0.001) except KeyboardInterrupt: # 上下文管理器会自动关闭线程池,清理资源 raise if __name__ == '__main__': main()
补充说明
如果优化后依然看到RSS缓慢上涨,大概率是Python内存分配器的特性:Python回收的小块内存会保留在进程内的内存池复用,不会立刻归还给操作系统,这种情况内存不会无限上涨,会稳定在一个合理的阈值,不属于泄漏。如果内存还是无限增长,再排查是否有其他全局对象持有任务的引用。
内容的提问来源于stack exchange,提问作者SynackSA
相关产品推荐
相关产品推荐

