线程池适合I/O密集型任务,进程池适合CPU密集型任务;合理设置池大小,用submit+as_completed实现灵活调度,并注意资源清理与异常处理。

线程池适合I/O密集型任务,别拿它干CPU重活
Python的GIL(全局解释器锁)让多线程无法真正并行执行CPU密集型代码。所以用ThreadPoolExecutor处理网络请求、文件读写、数据库查询等I/O操作才高效。比如并发抓10个网页,用线程池能显著缩短总耗时;但若用来做矩阵运算或图像处理,开10个线程和单线程跑得差不多,还白占调度开销。
进程池才是CPU密集型任务的正确打开方式
ProcessPoolExecutor绕过GIL限制,每个子进程拥有独立解释器和内存空间,适合计算密集场景。注意三点:
• 进程启动比线程慢,频繁创建销毁反而拖累性能,建议复用已有进程池
• 参数和返回值必须可序列化(pickle),不能传lambda、嵌套函数或带绑定方法的对象
• 进程间不共享内存,需通过Manager或Queue通信,避免直接修改全局变量
合理设置池大小,别盲目堆数量
线程池默认大小通常是min(32, os.cpu_count() + 4),对I/O任务够用;但实际应按并发瓶颈调优:
• 网络请求为主:线程数可设为50~200,取决于目标服务器吞吐和本地连接池限制
• 文件IO:受磁盘寻道和系统句柄数影响,一般20~50较稳妥
• 进程池大小通常设为cpu_count()或略高(如+1~2),再多只会增加上下文切换开销
用submit+as_completed实现灵活任务调度
比起map的同步阻塞模式,submit配合concurrent.futures.as_completed更利于实时响应和错误处理:
• 提交任务后立即获得Future对象,可随时检查状态、取消或加超时
• as_completed按完成顺序yield结果,无需等待最慢任务
• 配合try/except捕获单个任务异常,不影响其他任务继续执行
资源清理与异常传播要主动管
池对象不是“用了就扔”,尤其在长期运行服务中:
• 用with语句确保shutdown(wait=True)被调用,释放子进程/线程资源
• 捕获BrokenProcessPool等异常,说明子进程意外退出,需重建池
• 主进程抛出的异常不会自动传回子进程,调试时记得在worker函数里加日志或用future.exception()检查










