用同样4个CPU密集任务和4个IO密集任务分别跑ThreadPoolExecutor和ProcessPoolExecutor,验证线程池和进程池各自的适用场景。
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 版本
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。
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虽然同样能做到并发等待,但要多付出创建/管理独立进程的开销(进程启动比线程重得多),在这种轻量任务上这份开销反而让它比线程池慢。
每一步都是本文上面用concurrent.futures跑出来的原始耗时。
ThreadPoolExecutor vs ProcessPoolExecutor:对比CPU密集/IO密集两种场景