一个批量算图片哈希的脚本,单进程跑 40 分钟。改成 8 线程,只快了 15%,top 里 CPU 始终只有一个核在满。

这是 GIL 造成的:同一时刻只有一个线程执行字节码。多线程对 CPU 密集任务几乎无效,线程切换反而多花时间。

先判断任务类型

CPU 密集的活,比如计算、压缩、加解密、图像处理,用多进程。IO 密集的活,比如网络请求、读写磁盘、查数据库,用多线程或协程,等待时 GIL 会释放,能真正并发。

多进程怎么写

from concurrent.futures import ProcessPoolExecutor

进程数取 os.cpu_count(),超过核数只增加切换开销。每个进程内存独立,参数要 pickle 序列化传过去,几百 MB 的数组传一次就要好几秒,这种场景改用共享内存,或者按文件分片处理。

Windows 上多进程必须放在 if __name__ == '__main__' 下面启动,否则子进程会重新导入模块,无限开进程把机器拖死。

并行不等于线性加速

Amdahl 定律:串行部分占 10%,核数再多最多快 10 倍。8 核实测跑出 5 倍算正常,别拿核数当承诺去汇报。

共享资源是另一个坑。同时写一个日志文件、抢同一行数据库记录,锁一上,并行立刻退化成串行,还多了锁竞争的开销。

任务本身也要够大。切成一万个小任务,进程间通信的时间盖过计算,还不如单进程。粗粒度切分,每个任务跑几百毫秒,才划算。

进程池还有隐性成本:子进程启动要重新导入模块,import 重的库一次两三秒。任务总共才跑十秒,启动开销就吃掉一半,这种短任务用线程反而划算。

先 profile 找瓶颈,能并行的部分才并行。把等待改成异步,往往比加进程省资源;一台机器扛不住,先看代码再谈加机器。