并行I/O:线程优于进程 | AI生成和翻译

Home 2026.05

很好的问题。让我从基本原理开始分析。

GIL(全局解释器锁)

CPython有一个锁,阻止两个Python字节码指令同时在多个CPU核心上运行。这就是著名的GIL。它的含义是:

git pull 时发生了什么?

你的Python代码调用 subprocess.run(["git", "pull", ...])。这个过程如下:

Python线程 → fork() → exec("git") → git连接到GitHub → 等待socket → 数据到达 → 返回Python

关键部分:当git作为子进程运行时,你的Python线程阻塞在 os.waitpid()——它在内核中休眠,而不是执行Python字节码。当线程在内核中休眠时,它会释放GIL。其他线程可以自由运行。

所以,如果用8个线程执行git pull:

线程1: fork git → 休眠(GIL释放)──────────── 唤醒 → 完成
线程2: fork git → 休眠(GIL释放)──────────── 唤醒 → 完成
线程3: fork git → 休眠(GIL释放)────────── 唤醒 → 完成
...
全部8个git子进程在8个操作系统进程上并行运行

GIL无关紧要,因为你的Python线程99%的时间都在休眠。它们只是短暂唤醒以记录结果。

为什么多进程在这里浪费资源:

多进程:内存中有8个完整的Python解释器
  - 每个约30-50MB RSS
  - 进程间需pickle/unpickle数据(序列化开销)
  - 生成成本:fork() + import世界

多线程:一个进程中的8个轻量级线程
  - 每个约8KB栈
  - 共享内存(无需序列化)
  - 生成成本:仅创建线程

对于网络I/O,两者提供的并行性相同(操作系统无论如何都会调度8个git进程)。但线程在内存和启动上便宜100倍。

什么时候需要多进程?

当瓶颈是CPU时——Python字节码在紧密循环中执行。GIL阻止两个线程同时执行字节码。例如:

# 这不会用线程加速——GIL阻止并行执行
def cpu_heavy(n):
    return sum(i * i for i in range(n))

# 这可以用多进程加速——每个进程有自己的GIL
with ProcessPoolExecutor(8) as pool:
    pool.map(cpu_heavy, [10**7] * 8)

决策树:

工作是否是I/O密集型(网络、磁盘、子进程)?
  → 是 → 线程(便宜,GIL无关紧要)
  → 否 → 是否是CPU密集型(数学、解析、ML)?
    → 是 → 多进程(绕过GIL)
    → 否 → 可能不需要并行

对于 git pull,它100%是网络等待。线程是完美的。多进程也能工作,只是浪费内存而没有任何好处。


Back Donate