为什么CPython要有GIL

一、为什么 CPython 要有 GIL?(追问点)

1.1 历史原因

CPython 诞生于 1991 年,当时多核 CPU 还不存在。GIL 是一个简单高效的设计决策,不是”错误”。

1.2 GIL 解决的两个核心问题

① 保护 CPython 对象的内存安全

 
# Python 中一切皆对象,引用计数是核心机制
 
a = [1, 2, 3]   # 创建列表对象,引用计数 = 1
 
b = a           # 引用计数 = 2
 
del a           # 引用计数 = 1
 

通俗比喻:引用计数就像一个共享白板上的计数器——有人要看这块白板,计数器 +1;不看了,计数器 -1;减到 0 就把白板扔掉。

问题来了:如果两个人同时看到计数器是 1,都去 -1,结果变成 -1(或者一个人 -1 变成 0 白板被扔了,另一个人还试图在上面写字)——内存泄漏或者野指针就来了。这就是竞态条件(race condition)

GIL 的解决方案简单粗暴:同一时刻只让一个人碰计数器,其他人排队。虽然笨,但管用。

② 让 C 扩展库更容易编写

大量 Python 生态的 C 扩展库(NumPy、Pandas)都假设”同一时刻只有一个线程执行 Python 代码”。

通俗比喻:就像你租了一间厨房(写 C 扩展库),房东(GIL)保证”同一时刻只有你一个人用厨房”。你可以放心地把菜刀随便放、锅不盖盖子——反正没人会碰你的东西。如果房东突然说”以后厨房共用,自己锁柜子”,那你所有厨具的摆放方式都得重来——整个生态都得改

1.3 简答:为什么 Python 有 GIL?

GIL 是 CPython 的历史设计选择,主要两个原因:1)保护引用计数机制的线程安全,防止多线程并发修改对象导致内存问题;2)简化 C 扩展库的开发,让第三方库不需要考虑线程安全。代价是多线程无法利用多核 CPU 并行执行 Python 代码。


速记卡(面试闪卡)

Q1:一句话讲清「为什么CPython要有GIL」到底是什么?

A:GIL 是 CPython 的全局锁,同一时刻只放一个线程跑 Python 代码;换来引用计数线程安全和生态简单,代价是多线程吃不满多核。

Q2:历史原因——多核还没出生 —— 怎么理解?

A:GIL 诞生于 1991 年,那时多核 CPU 还不存在,它是简单高效的设计而非错误。像老式单车道马路,当年车少够用,如今车多了才显得堵。

Q3:保护引用计数的线程安全 —— 怎么理解?

A:Python 一切皆对象,靠引用计数决定何时回收;并发改计数会出竞态(两人同时 -1 变 -1 或野指针)。GIL 让”同一时刻只有一个人碰计数器”,简单粗暴但管用。像公共白板计数器,一次只许一人改,避免数错。

Q4:让 C 扩展库更好写 —— 怎么理解?

A:NumPy、Pandas 等 C 扩展都假设”同一时刻只有一个线程跑 Python 代码”,所以随便放厨具不锁柜子。若去掉 GIL,整个生态得重写线程安全逻辑。像房东保证厨房独享,你才敢乱放刀。

Q5:代价与绕过思路 —— 怎么理解?

A:代价是 CPU 密集型多线程无法利用多核——一个线程跑时其他干等。但 I/O 密集型不受影响(等待时释放 GIL);真要并行算多用多进程或 C 扩展释放 GIL。像一顿饭只能一人掌勺,但洗菜切菜可并行外包。

Q6:核心速记主线有哪些?

  • GIL 是历史设计,多核时代前的简单选择

  • 保护引用计数线程安全,防竞态

  • 简化 C 扩展开发,生态无需写锁

  • 代价:多线程吃不满多核;I/O 型无碍,计算型用多进程

口诀

A:GIL 全局一把锁,同刻只放一线程;

引用计数得保护,扩展无需自己锁。

多核时代显短板,计算并行靠多进程;

IO 等待会释放,异步并发不耽搁。

相关链接