bigywater.tech
Python · 6

concurrent.futures:线程池和进程池到底怎么调度任务

用同样4个CPU密集任务和4个IO密集任务分别跑ThreadPoolExecutor和ProcessPoolExecutor,验证线程池和进程池各自的适用场景。

3.1倍
CPU密集任务进程池比线程池快的倍数
线程池更快
IO密集任务不需要进程池的额外开销
同一套API
concurrent.futures让两种池的调用方式完全一致

做的事:CPU密集和IO密集,各跑一遍线程池和进程池

4个任务,用ThreadPoolExecutor和ProcessPoolExecutor各跑一遍,CPU密集和IO密集各测一次。

代码def cpu_task(n): x = 0 for i in range(n): x += i*i return x def io_task(sec): time.sleep(sec) return sec with ThreadPoolExecutor(max_workers=4) as ex: list(ex.map(cpu_task, [N]*4)) # 以及 io_task 版本 with ProcessPoolExecutor(max_workers=4) as ex: list(ex.map(cpu_task, [N]*4)) # 以及 io_task 版本

验证:CPU密集任务,进程池碾压线程池

4个纯计算任务,理论上4核并行能有明显提速。

结果CPU密集任务x4, ThreadPoolExecutor: 耗时 2.1256s CPU密集任务x4, ProcessPoolExecutor: 耗时 0.6813s(快约3.1倍)

ThreadPoolExecutor底层就是Python的threading.Thread,同样受GIL限制——4个CPU密集任务排队式地占用同一把锁轮流执行,几乎没有从"池"里获得任何并行收益。ProcessPoolExecutor每个worker是独立进程,各自有自己的GIL,4个任务能同时占用4个CPU核心,耗时降到接近1/3。

验证:IO密集任务,线程池反而更快

4个"只需要等待"的任务,各sleep 0.3秒。

结果IO密集任务x4(各sleep 0.3s), ThreadPoolExecutor: 耗时 0.3044s IO密集任务x4(各sleep 0.3s), ProcessPoolExecutor: 耗时 0.3829s(反而更慢)

IO密集场景下,ThreadPoolExecutor耗时几乎就是单个任务的耗时(0.3s左右)——因为 time.sleep 这类阻塞调用在等待期间会释放GIL,让其他线程有机会执行,4个线程能同时进入"等待"状态,几乎不排队。ProcessPoolExecutor虽然同样能做到并发等待,但要多付出创建/管理独立进程的开销(进程启动比线程重得多),在这种轻量任务上这份开销反而让它比线程池慢。

这就是为什么"CPU密集用进程池,IO密集用线程池"是成立的经验法则:CPU密集任务线程池被GIL卡住,进程池才能并行;IO密集任务线程池已经足够快(等待时GIL会被释放),进程池的额外开销是净损失,不值得。

分步演示:4种组合的耗时对比

每一步都是本文上面用concurrent.futures跑出来的原始耗时。

CPU密集用进程池,IO密集用线程池,数字验证经验法则
这一步新验证的组合

    本文用到的工具

    • 本机 Python 3.14.7,10核CPU
    • ThreadPoolExecutor vs ProcessPoolExecutor:对比CPU密集/IO密集两种场景
    京ICP备2026067502号-1京公网安备11010502063487号