穿透 Python GIL 的迷雾:它到底锁的是什么?为什么 CPython 仍坚持多线程?

2025博客之星年度评选已开启 10w+人浏览 1.5k人参与

穿透 Python GIL 的迷雾:它到底锁的是什么?为什么 CPython 仍坚持多线程?

在我这些年教授 Python、参与高性能系统开发的经历中,最常被问到的问题之一就是:

“GIL 到底锁的是什么?是不是锁住了所有线程?”

“既然 GIL 会阻塞多线程并行,那 CPython 为什么还要保留多线程?”

“多线程在 Python 中还有意义吗?”

这些问题背后,是 Python 开发者对性能、并发和底层机制的深深困惑。GIL(Global Interpreter Lock,全局解释器锁)几乎是 Python 世界里最具争议的设计之一,它既是历史遗留,又是现实妥协;既是性能瓶颈,又是稳定基石。

这篇文章,我将带你从 Python 的发展背景讲起,逐步深入 GIL 的本质、触发机制、设计原因与工程实践价值,并结合大量代码示例与真实案例,帮助你真正理解:

✅ GIL 到底锁住了什么
✅ 为什么 CPython 仍然保留多线程
✅ 多线程在 Python 中的正确使用方式
✅ 如何绕过 GIL 获得真正的并行

让我们从头开始。


一、开篇:Python 的发展与 GIL 的诞生

Python 自 1991 年诞生以来,以“简洁、优雅、可读性强”著称。它的设计哲学之一是:

让开发者专注于业务逻辑,而不是内存管理与线程安全。

在 Python 的早期版本中,内存管理依赖引用计数(Reference Counting)。引用计数的更新不是原子操作,因此在多线程环境下会产生竞争条件(Race Condition)。

为了避免复杂的锁设计、提升解释器稳定性、降低开发成本,Python 的作者 Guido van Rossum 做了一个关键决定:

用一个全局锁保护解释器内部状态。

这就是 GIL 的起源。


二、GIL 到底锁的是什么?(核心问题)

很多人以为 GIL 锁住的是:

  • 所有线程
  • 所有 Python 对象
  • 所有 CPU 核心

但这都是误解。

GIL 锁住的不是 Python 对象,而是整个解释器状态(Interpreter State)

更准确地说:

GIL 锁住的是 CPython 的字节码执行器(Bytecode Interpreter)。

也就是说:

  • 只有持有 GIL 的线程才能执行 Python 字节码
  • 其他线程即使被调度,也无法执行 Python 层面的运算
  • GIL 不锁 I/O,不锁 C 扩展,不锁系统调用

✅ GIL 的本质:保护引用计数与对象模型

CPython 的对象模型不是线程安全的:

  • 引用计数不是原子操作
  • 内存分配器不是线程安全的
  • 对象内部状态不是线程安全的

因此:

GIL 是 CPython 为了保证对象模型一致性而加的全局互斥锁。


三、GIL 的工作机制:什么时候会释放?什么时候会阻塞?

GIL 的行为可以总结为:

一个线程持有 GIL → 执行 Python 字节码
其他线程等待 → 无法执行 Python 字节码

但 GIL 并不是一直不放,它会在以下情况释放:


✅ 1. I/O 操作时释放 GIL(最重要)

例如:

import threading
import time

def task():
    time.sleep(1)  # I/O 操作,释放 GIL
    print("done")

for _ in range(10):
    threading.Thread(target=task).start()

time.sleep() 会释放 GIL,因此多个线程可以并发执行。

这也是为什么:

✅ Python 多线程适合 I/O 密集型任务
❌ 不适合 CPU 密集型任务


✅ 2. C 扩展主动释放 GIL

例如 NumPy:

import numpy as np

def compute():
    a = np.random.rand(10000, 10000)
    b = a @ a  # C 扩展内部释放 GIL

NumPy 的矩阵运算在 C 层面执行,不受 GIL 限制,可以真正并行。


✅ 3. 线程切换时释放 GIL(每隔一定字节码执行次数)

Python 3.2 之前:

  • 每执行 100 条字节码 切换一次线程

Python 3.2 之后:

  • 改为基于时间片(默认 5ms)

四、为什么 CPython 仍然保留多线程?

这是很多人最困惑的问题:

“既然 GIL 限制了并行,那 Python 为什么还要多线程?”

答案有三个层面。


✅ 1. 多线程对 I/O 密集型任务非常有效

例如:

  • 网络请求
  • 文件读写
  • 数据库访问
  • 爬虫
  • 日志处理

这些操作会释放 GIL,因此多线程可以真正并发。

示例:

import requests
import threading

def fetch(url):
    print(requests.get(url).status_code)

urls = ["https://example.com"] * 20

for u in urls:
    threading.Thread(target=fetch, args=(u,)).start()

多线程可以显著提升吞吐量。


✅ 2. 多线程比多进程更轻量

  • 线程共享内存
  • 创建成本低
  • 上下文切换快
  • 适合高并发 I/O

在 Web 服务中,多线程仍然是主流模型之一。


✅ 3. CPython 的生态大量依赖线程

例如:

  • logging 模块内部使用线程
  • asyncio 的事件循环使用线程池
  • concurrent.futures.ThreadPoolExecutor
  • 各种 C 扩展内部使用线程

如果移除线程,整个生态将崩溃。


五、GIL 的误区:你以为它限制了所有并行,其实没有

以下情况不受 GIL 限制:


✅ 1. I/O 操作(会释放 GIL)

例如:

  • 网络请求
  • 文件读写
  • sleep
  • socket
  • subprocess

✅ 2. C 扩展(主动释放 GIL)

例如:

  • NumPy
  • Pandas
  • TensorFlow
  • PyTorch
  • Pillow

这些库内部使用 C/C++,可以真正多核并行。


✅ 3. 多进程(完全绕过 GIL)

from multiprocessing import Pool

def f(x):
    return x * x

with Pool(4) as p:
    print(p.map(f, range(10)))

每个进程有自己的 GIL,因此可以真正并行。


六、实战:如何绕过 GIL 获得真正的并行?

下面给出工程实践中最常用的三种方式。


✅ 方式 1:使用多进程(multiprocessing)

适合 CPU 密集型任务:

from multiprocessing import Pool

def cpu_task(n):
    return sum(range(n))

with Pool() as p:
    print(p.map(cpu_task, [10_000_000] * 4))

✅ 方式 2:使用 C 扩展(NumPy、Cython、PyPy)

例如 NumPy:

import numpy as np

a = np.random.rand(20000, 20000)
b = a @ a  # 真正并行

✅ 方式 3:使用 asyncio(适合 I/O)

import asyncio
import aiohttp

async def fetch(session, url):
    async with session.get(url) as resp:
        return await resp.text()

async def main():
    async with aiohttp.ClientSession() as session:
        tasks = [fetch(session, "https://example.com") for _ in range(20)]
        await asyncio.gather(*tasks)

asyncio.run(main())

七、GIL 的未来:会被移除吗?

Python 社区一直在探索移除 GIL 的可能性。

✅ PEP 703:可选无 GIL 的 CPython(Huge Progress)

2023 年,Sam Gross 提出:

让 CPython 支持无 GIL 模式(可选)

目前已经合并到 Python 3.13 的实验性分支。

未来 Python 可能会:

  • 默认保留 GIL(兼容性)
  • 提供无 GIL 模式(高性能)

这是 Python 历史上最重要的变革之一。


八、总结:GIL 并不是 Python 的“罪人”,而是历史的妥协

本文从多个角度深入解析了 GIL:

✅ GIL 锁住的是解释器状态,而不是 Python 对象
✅ GIL 的本质是保护引用计数与对象模型
✅ 多线程仍然适用于 I/O 密集型任务
✅ C 扩展与多进程可以绕过 GIL
✅ Python 未来可能支持无 GIL 模式

GIL 是 Python 的“保护伞”,也是“枷锁”。理解它,你才能真正写出高性能的 Python 程序。


九、互动时间

我很想听听你的经验:

  • 你在 Python 多线程中遇到过哪些坑
  • 你认为 GIL 是 Python 的优势还是劣势
  • 你希望未来的 Python 如何处理并发

欢迎留言交流,我们一起探索 Python 的更多可能性。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

铭渊老黄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值