CPU密集型任务应选multiprocessing而非threading,因GIL使多线程无法并行;I/O密集型用threading或asyncio;concurrent.futures提供统一接口,按场景选ThreadPoolExecutor或ProcessPoolExecutor。

线程 vs 进程:CPU 密集型任务别用 threading
Python 的 threading 模块在 I/O 等待时能“假装”并发,但遇到计算密集型任务(比如数值循环、图像处理),GIL 会强制串行执行,多个线程几乎不提速,还增加上下文切换开销。
真正需要并行计算时,必须用 multiprocessing——它启动独立进程,绕过 GIL,每个进程有完整 Python 解释器和内存空间。
-
multiprocessing进程间通信成本高:不能直接共享变量,得靠Queue、Pipe或Manager,传大对象(如 NumPy 数组)会触发序列化/反序列化,慢且吃内存 -
threading更适合:HTTP 请求、文件读写、数据库查询等 I/O 占比高的场景 - 启动进程比启动线程慢得多,频繁创建/销毁进程(比如每秒几百次)反而拖累性能
协程不是“更轻量的线程”:它依赖 await 主动让出控制权
asyncio 不是自动并发,它只在碰到 await 且对应对象是可等待对象(如 asyncio.sleep()、aiohttp.ClientSession.get())时才切换任务。普通函数调用、CPU 计算、同步 I/O(如 time.sleep()、requests.get())都会阻塞整个事件循环。
- 混用同步和异步代码是常见翻车点:比如在
async def函数里写time.sleep(5),整个协程队列就卡住 5 秒 - 第三方库必须明确支持异步(如
aiohttp、aiomysql),用requests就等于退化成单线程 -
asyncio.to_thread()(Python 3.9+)或loop.run_in_executor()可把阻塞调用扔进线程池,但只是“补救”,不是设计初衷
concurrent.futures 是最实用的统一入口
不用纠结该选 threading 还是 multiprocessing,直接用 concurrent.futures 提供的抽象接口,切换底层执行器只需改一行代码。
立即学习“Python免费学习笔记(深入)”;
- 用
ThreadPoolExecutor处理 I/O 密集型任务:HTTP 批量请求、日志解析等 - 用
ProcessPoolExecutor处理 CPU 密集型任务:科学计算、编码转码、批量数据清洗 -
max_workers别盲目设大:线程池一般设为 CPU 核数 × 2~5(I/O 越多越可放大),进程池通常设为os.cpu_count()或略少,超了反而因调度争抢变慢 - 提交任务用
submit()得到Future对象,用as_completed()获取完成顺序,别用result()盲等——它会阻塞当前线程/协程
真实项目里,三者经常嵌套使用
没有“银弹”。比如一个 Web 后端服务:主流程用 asyncio 接收请求;对每个请求,用 ProcessPoolExecutor 跑图像识别模型(CPU 密集);同时用 ThreadPoolExecutor 并发调多个外部 API(I/O 密集);所有结果再交回事件循环组装响应。
- 跨层传递数据要小心:从进程里返回的
numpy.ndarray可能变成list或复制失败,优先用原生 Python 类型或显式序列化 -
asyncio和multiprocessing一起用要避免在子进程中再启事件循环——子进程默认没运行asyncio,也别在if __name__ == '__main__'外直接调asyncio.run() - 调试时别只看 CPU:用
psutil查进程/线程数,用asyncio.all_tasks()看挂起的协程,错误常藏在“以为在跑,其实卡死”的地方



















