AIGC标识 Python 的 GIL 到底锁住了什么?拆开线程调度与那段"串行化的 CPU"

很多人学 Python 多线程时都被泼过冷水:"Python 的多线程是假的,因为有 GIL。"这句话流传很广,但真正被锁住的到底是什么,多数人讲不清。今天我把它拆开,再用一段实测告诉你:什么时候 GIL 在拖后腿,什么时候它其实无所谓。

一、GIL 不是 Python 语言的锁,是 CPython 的锁

先纠正一个常见误解:GIL(Global Interpreter Lock,全局解释器锁)是 CPython 这个具体实现加的,不是 Python 语言规范要求的。Jython、IronPython 没有 GIL,PyPy 也在做无 GIL 的实验版本。所以严格说,"Python 有 GIL"这句话只在你用 CPython 时才成立。

那它锁的是什么?一句话:任何时刻,只有一个线程能持有这把锁去执行 Python 字节码。它本质上就是一个操作系统级的互斥锁(在 Linux/Windows 上底层是 pthread mutex),谁拿到锁谁才能跑 Python 代码。

二、GIL 怎么"轮流转"的

CPython 并不会让一个线程一直霸占 GIL。它设了一个切换间隔,默认 5 毫秒(可以用 sys.getswitchinterval() 查看):

import sys
print(sys.getswitchinterval())   # 默认 0.005,即 5 毫秒

解释器每执行若干字节码,就会检查是否到了切换时间。到了,就释放 GIL → 操作系统调度 → 另一个线程抢到 GIL → 继续跑。看起来是"并发"的,但同一瞬间只有一个线程在真正执行 Python 字节码。

关键点在于:这个切换对 I/O 等待是友好的,对 CPU 密集计算是无效的。线程在等网络/磁盘时本来就要让出 CPU,GIL 顺势释放,别的线程就能上来;但两个线程都在死算 CPU,切换只是"轮流占锁",总工作量没少,还多出了切换开销。

三、实测:CPU 密集任务,多线程到底加没加速

我在本机跑了同一个纯计算任务(累加 i*i),分别用单线程、2 线程、2 进程各跑两遍:

def cpu_task(n):
    s = 0
    for i in range(n):
        s += i * i
    return s
方式 耗时(2 次合计) 相对单线程
单线程 5.23 s 1.00×
2 线程(ThreadPoolExecutor) 4.99 s 0.95×(几乎没加速)
2 进程(ProcessPoolExecutor) 3.37 s 0.64×(约 1.6× 加速)

这个结果很说明问题:

  • 2 线程跑 CPU 密集任务,时间和单线程几乎一样(0.95×,甚至略慢因为切换开销)。这就是 GIL 在起作用——两个线程抢同一把锁,CPU 算力没有被叠加。
  • 2 进程却真的快了约 1.6 倍。因为每个进程有自己独立的 GIL,操作系统能把两个进程放到不同核上真正并行。

我个人的经验判断:GIL 的杀伤面其实比"多线程没用"这句话小得多。它只在"纯 Python 代码的 CPU 密集"这一亩三分地里作妖;一旦你的瓶颈是网络、磁盘、或者底层 C 扩展(NumPy 这类会在计算时主动释放 GIL),多线程照样能打满。

四、那什么时候还该用多线程?

  1. I/O 密集型:爬虫、Web 服务、读文件。线程在 recv/read 时释放 GIL,并发轻松。
  2. 配合释放 GIL 的 C 扩展:NumPy、Pillow、OpenCV 的底层循环会 Py_BEGIN_ALLOW_THREADS 释放锁,多线程能真正并行。
  3. 想避开多进程的开销:进程间不共享内存,传大数据要走序列化(pickle),成本高;线程共享内存,协作更轻。

五、绕开 GIL 的三条正路

方案 适用 代价
multiprocessing CPU 密集、数据可分片 内存翻倍、IPC 开销
asyncio 高并发 I/O 要改成协程写法
C 扩展 / Cython(nogil) 极致性能 写 C,门槛高

结语

GIL 锁住的,是"同一进程里多个 Python 线程同时跑字节码"的能力,不是 Python 的性能天花板。看明白它是把 OS 级互斥锁、默认 5 毫秒轮转、对 I/O 友好对 CPU 冷漠,你就不会再一刀切地说"Python 多线程没用"——也不会在 CPU 密集任务上白开一堆线程,最后发现和单线程一样慢。

posted @ 2026-10-30 09:18  小小张说故事  阅读(13)  评论(0)    收藏  举报