为何Python中ThreadPoolExecutor内fork的函数近乎顺序执行?
问题分析:ThreadPoolExecutor结合fork后任务顺序执行的原因
问题背景
我原本想在Python类方法中结合AsyncIO与ProcessPoolExecutor,但遭遇pickle序列化错误(无法序列化函数或同类方法)。于是尝试在类的__init__方法中初始化ThreadPoolExecutor,提交一个立即调用os.fork()的test函数以规避GIL。fork后父进程仅等待子进程退出(执行IO操作),子进程处理CPU密集任务,但多次调用该方法后发现test的不同调用近乎顺序执行,请问这是为什么?
复现代码
import os import asyncio import concurrent.futures import starlette.requests class Foo(): def __init__(self): self._tp = concurrent.futures.ThreadPoolExecutor() def test(self): child=os.fork() if(child==0): os._exit(os.EX_OK) else: os.waitpid(child, 0) return "ok" async def __call__(self, request: starlette.requests.Request): json_request = await request.json() res = await asyncio.wrap_future(self._tp.submit(self.test)) return res
原因与解决思路
核心原因:线程池默认只有1个工作线程
你初始化ThreadPoolExecutor时没有指定max_workers参数,它的默认值是1。这意味着线程池里只有一个线程在处理所有提交的任务,所有test调用都会被放进队列,由这一个线程依次执行,自然表现为顺序运行。
额外问题说明
就算你调大线程数,当前这种fork+waitpid的写法也不是最优解:
- 每个线程调用
os.waitpid(child, 0)会阻塞线程,直到子进程结束。虽然多线程能实现子进程并行,但这种手动管理进程的方式比直接用ProcessPoolExecutor繁琐且容易出问题。 - 你之前遇到的pickle序列化问题,本质是类方法无法被标准pickle序列化,解决起来很简单——把CPU密集逻辑抽成独立的顶层函数即可,完全没必要绕弯子用线程池+fork。
修复方案
方案1:调整线程池大小(仅适配当前写法,不推荐)
初始化线程池时指定足够的线程数,比如根据CPU核心数设置:
self._tp = concurrent.futures.ThreadPoolExecutor(max_workers=4) # 替换为你的CPU核心数
方案2:改用ProcessPoolExecutor解决序列化问题(推荐)
把CPU密集逻辑抽成顶层函数,直接用ProcessPoolExecutor配合AsyncIO的run_in_executor:
import asyncio from concurrent.futures import ProcessPoolExecutor import starlette.requests # 把CPU密集逻辑抽成独立顶层函数 def cpu_task(data): # 这里写你的CPU密集处理逻辑 return f"处理结果:{data}" class Foo(): def __init__(self): self._pp = ProcessPoolExecutor() async def __call__(self, request: starlette.requests.Request): json_request = await request.json() # 用run_in_executor提交进程池任务 res = await asyncio.get_event_loop().run_in_executor( self._pp, cpu_task, json_request ) return res
内容的提问来源于stack exchange,提问作者markiZZ
相关产品推荐
相关产品推荐

