CPU密集型任务用ProcessPoolExecutor,I/O密集型用ThreadPoolExecutor;max_workers需按负载类型调优;线程共享内存需锁保护,进程间不共享需Manager;异常在result()才抛出,cancel()对进程基本无效。

什么时候该用 ThreadPoolExecutor 而不是 ProcessPoolExecutor
CPU 密集型任务(如数值计算、图像处理)用 ProcessPoolExecutor,I/O 密集型任务(如 HTTP 请求、文件读写)用 ThreadPoolExecutor。Python 的 GIL 会让多线程在 CPU 密集场景下几乎不提速,而多进程能真正并行。
常见误判点:
- 误把“耗时长”等同于“CPU 密集”——比如压缩一个大文件看似耗时,但实际是 I/O + 少量 CPU,
ThreadPoolExecutor可能更轻量; - 在 Web 爬虫中混用同步阻塞库(如
requests)和ProcessPoolExecutor,反而因进程启动开销和序列化成本变慢; - 未考虑对象可序列化性:传给
ProcessPoolExecutor的函数和参数必须能被pickle,闭包、lambda、类实例方法常直接报AttributeError: Can't pickle local object。
max_workers 设多少才合理
ThreadPoolExecutor 的 max_workers 默认是 min(32, (os.cpu_count() or 1) + 4),但这个值对 I/O 任务往往偏小;ProcessPoolExecutor 默认是 os.cpu_count(),对 CPU 密集任务通常够用。
调优建议:
立即学习“Python免费学习笔记(深入)”;
- I/O 密集:从 10–100 开始试,观察系统连接数、线程上下文切换频率(
pidstat -t)、目标服务限流响应; - CPU 密集:一般不超过
os.cpu_count(),超了反而因调度竞争降低吞吐; - 混合负载(如先请求再计算):优先拆成两层——用线程池做 I/O,结果交给进程池计算,避免单池承担两类压力。
共享状态与资源竞争怎么避坑
线程间共享内存,进程间默认不共享——这是最易出错的分水岭。
典型陷阱:
- 在线程池里修改全局变量或类属性,可能引发竞态,需加
threading.Lock; - 在进程池里试图修改主进程的列表、字典,修改不会回传(每个进程有独立内存副本),要用
multiprocessing.Manager或显式返回+合并; - 数据库连接、文件句柄不能跨进程复用:
ProcessPoolExecutor中每个子进程需自行初始化连接,否则报sqlite3.ProgrammingError: SQLite objects created in a thread can only be used in that same thread类错误; - 日志写入冲突:多个线程/进程同时写同一文件,需用
logging.handlers.RotatingFileHandler配合delay=True,或改用支持并发的日志库(如concurrent-log-handler)。
异常传播与任务取消的实际表现
submit() 返回的 Future 对象,在调用 result() 时才会抛出子线程/子进程里的异常——这点常被忽略,导致错误静默丢失。
关键差异:
- 线程池中未捕获异常会终止该线程,但不影响其他任务;
- 进程池中子进程崩溃(如段错误、
SystemExit)会导致Future.result()抛出BrokenProcessPool,整个池不可再用; -
Future.cancel()在线程池中成功率高,在进程池中基本无效——因为 Python 无法强制终止 OS 进程,只能标记“不执行”,若任务已开始,会继续跑完; - 想实现超时控制,统一用
future.result(timeout=5),别依赖cancel()。
真正难处理的是子进程内发生的 C 扩展崩溃、死循环或阻塞系统调用,这类问题没有银弹,得靠外部监控或封装为带信号超时的子进程(如 subprocess.run(..., timeout=...))来兜底。










