Skip to content

01 · GIL 与多线程:Python 为什么「多线程不快」 ​

学完你能说出:

  • GIL 是什么:同一时刻只允许一个线程执行 Python 字节码。
  • 为什么 IO 密集型任务用多线程仍然有效,CPU 密集型却没用。
  • CPU 密集型任务该用什么:多进程。

先看地图 ​

这是「Python 与工程」模块的第一篇,接着进程与线程讲。操作系统篇讲的是通用原理,这一篇讲 Python 特有的一个限制。AI 应用岗面试问 Python,这题出现频率最高。

本篇讲 4 个点:① GIL 是什么 → ② 为什么有 GIL → ③ IO 密集 vs CPU 密集 → ④ 怎么选:线程、进程、协程。

一句话本质 ​

GIL(全局解释器锁)= CPython 解释器里的一把大锁:同一时刻,只有拿到锁的那个线程能执行 Python 代码。

生活类比:只有一支麦克风的会议 ​

  • 会议室里有 4 个人(4 个线程),但只有一支麦克风(GIL)。谁拿着麦克风谁才能说话。
  • 如果大家都要一直说话(CPU 密集:一直在算),4 个人轮流用麦克风,总的说话时间和 1 个人差不多,没有变快。
  • 如果大家大部分时间在等别人回邮件(IO 密集:等网络、等磁盘),等的时候把麦克风放下,别人就能用。4 个人一起等,总时间大大缩短。

一步步看:两个实验(数字是示意) ​

实验 1:CPU 密集:计算 1 到 1 亿的平方和,做 4 次。

方式耗时
单线程,顺序做 4 次约 20 秒
4 个线程约 20 秒(甚至更慢,因为切换线程有开销)
4 个进程约 5 秒(4 核 CPU 真正同时算)

实验 2:IO 密集:请求 4 个网页,每个要等 1 秒。

方式耗时
单线程,顺序请求约 4 秒
4 个线程约 1 秒

为什么实验 2 有效?线程在等网络时会主动释放 GIL,别的线程就能拿到锁去发下一个请求。等待是可以重叠的。

为什么这么设计 ​

为什么 CPython 要有 GIL? ​

  • 问题:CPython 用引用计数管理内存:每个对象记着「有几个变量在用我」,计数归零就释放。如果多个线程同时修改同一个计数,可能算错,导致对象被提前释放(程序崩溃)或永远不释放(内存泄漏)。
  • 方案:加一把全局大锁,同一时刻只让一个线程执行,引用计数就不会被同时修改。
  • 好处:解释器实现简单、单线程速度快,C 扩展库也更容易写对。代价是多线程无法利用多核来加速计算。

一句话答法

GIL 是为了保护 CPython 的引用计数等内部状态不被多线程同时修改,用一把全局锁换来实现简单和单线程性能,代价是 CPU 密集型多线程无法并行。

为什么 IO 密集用线程,CPU 密集用进程? ​

  • 问题:GIL 让多线程无法同时计算。
  • 方案:
    • IO 密集:线程在等 IO 时会释放 GIL,多个等待可以重叠 → 用多线程(或协程)。
    • CPU 密集:每个进程有自己独立的解释器和 GIL,可以真正同时跑在多个核上 → 用多进程。
  • 好处:两类任务都能用上合适的并发方式。代价是进程更重:创建慢、不共享内存,数据要在进程之间传递。

一句话答法

IO 等待时会释放 GIL,所以 IO 密集用多线程有效;CPU 密集用多进程,每个进程有独立的 GIL,能真正利用多核。

怎么选:线程、进程、协程 ​

场景推荐原因
并发调用很多 API(比如批量调大模型)协程(asyncio)或多线程IO 密集,等待可以重叠;协程开销更小,见第 04 篇
数据处理、数值计算多进程,或用 NumPy 等库CPU 密集;NumPy 底层是 C 代码,计算时会释放 GIL
简单脚本单线程别过早优化

英文术语拆解 ​

术语拆开看一句话意思
GILGlobal 全局 + Interpreter 解释器 + Lock 锁全局解释器锁
CPythonC 语言实现的 Python官方、最常用的 Python 解释器
Reference Countingreference 引用 + counting 计数引用计数:记录对象被引用了几次
CPU-boundCPU + bound 受限于CPU 密集型:时间主要花在计算上
IO-boundIO(输入输出)+ boundIO 密集型:时间主要花在等待网络、磁盘上
threadingthread 线程 + -ingPython 的多线程标准库
multiprocessingmulti 多 + processing 处理(进程)Python 的多进程标准库

面试怎么问 ​

Q:Python 有 GIL,多线程还有用吗?

有用,适合 IO 密集型任务。线程在等待网络、磁盘时会释放 GIL,其他线程可以继续执行,所以多个 IO 等待能重叠。CPU 密集型任务因为 GIL 无法并行,要用多进程,或者用在计算时会释放 GIL 的 C 扩展(如 NumPy)。

Q:为什么会有 GIL?

CPython 用引用计数管理内存,GIL 保证同一时刻只有一个线程执行字节码,避免引用计数等内部状态的竞争,使解释器实现简单、单线程性能好。

Q:GIL 会被去掉吗?

Python 3.13 起提供了实验性的「自由线程」(无 GIL)构建版本,但默认版本仍然有 GIL,生态还在适配中。实际开发中仍按「IO 用线程或协程、CPU 用进程」来选。

容易混的地方 ​

  • 有 GIL ≠ 不需要锁:GIL 只保证单条字节码执行时不被打断,count += 1 这种「读 → 加 → 写」由多条字节码组成,多线程下照样会出错,共享数据仍然要加锁。
  • GIL 是 CPython 的特性,不是 Python 语言的规定:其他实现(如 Jython)没有 GIL。
  • 多进程不是万能的:进程间传数据要序列化,数据很大时开销可能超过并行带来的收益。

和其他知识的连接 ​

自测 3 问 ​

Q1:用「一支麦克风」类比,解释 GIL 对 CPU 密集和 IO 密集任务的不同影响。

点开看答案

GIL 像唯一的一支麦克风。CPU 密集时大家都要一直说话,只能轮流,总时间不变;IO 密集时大家大多在等回复,等的时候把麦克风放下,别人可以用,等待时间可以重叠,所以变快。

Q2:CPython 为什么要有 GIL?

点开看答案

为了保护引用计数等解释器内部状态不被多线程同时修改(否则对象可能被提前释放或泄漏),用一把全局锁换取实现简单和单线程性能。

Q3:要并发调用 100 次大模型 API,用什么?为什么?

点开看答案

用协程(asyncio)或多线程。这是 IO 密集任务,时间主要花在等网络响应上,等待时会释放 GIL,请求可以重叠进行。协程开销更小,适合大量并发。

✅ 验收清单 ​

  • 能用一句话说清 GIL 是什么
  • 能用「麦克风」类比解释为什么 IO 密集用线程有效、CPU 密集无效
  • 能讲清 GIL 存在的原因(问题 → 方案 → 好处 / 代价)
  • 能根据任务类型,选择线程、进程或协程,并说出理由
  • 能解释「有 GIL 为什么还需要加锁」

下一步该懂什么 ​