线程和 Python 的全局解释器锁
线程?GIL?对程序员来说,这就像饭店菜单上的“推荐菜”一样熟悉又不敢轻易点——听起来很香,吃起来却容易噎着。作为一个Python开发工程师,说到多线程你兴致勃勃,一提GIL你就心情复杂。这玩意儿到底是啥?它怎么就成了Python多线程性能的“天花板”?今天我们就从技术上把它掰扯清楚,别再跟面试官面面相觑了。
GIL 是个啥?你别慌,我来慢慢讲
Python的GIL(Global Interpreter Lock,全局解释器锁)是 CPython 解释器里设置的一个大锁。它的作用就像门卫:同一时间内,只允许一个线程进入执行 Python 字节码。
也就是说,不管你是开了两个线程,还是200个线程,只要是跑在 CPython 上,一次也只能有一个线程真正运行 Python 代码。就像电梯一样,不管你多想挤,门关上后一次就拉一个人。
“不是吧,那多线程岂不是白开了?” 你先别急,这得看你开线程是干嘛用的。
到底什么场景用线程才靠谱?
先说个直观感受:CPU 密集型任务别用线程了,那是徒劳。I/O 密集型任务可以考虑线程,这才是GIL的“容错区”。
来看个栗子:
CPU 密集型的“悲剧”
import threading
defcount():
x = 0
for _ in range(10 ** 7):
x += 1
threads = [threading.Thread(target=count) for _ in range(4)]
for t in threads:
t.start()
for t in threads:
t.join()
这段代码开了4个线程数数,结果是啥?跑得跟单线程差不多,甚至还更慢一点。**为啥?**因为这些线程得一个个排队抢GIL,等你数完这1亿,还不如直接用单线程省事。
I/O 密集型的“正道”
import threading
import requests
deffetch_url(url):
response = requests.get(url)
print(f"{url}: {response.status_code}")
urls = [
"https://httpbin.org/delay/2",
"https://httpbin.org/delay/2",
"https://httpbin.org/delay/2"
]
threads = [threading.Thread(target=fetch_url, args=(url,)) for url in urls]
for t in threads:
t.start()
for t in threads:
t.join()
这段就跑得飞快,仨线程同时去抓数据,虽然每个URL都慢慢等2秒,但因为线程在I/O等待时自动释放GIL,所以多个请求可以并发执行。
那多核CPU都浪费了吗?用不了了吗?
我觉得这才是GIL最“令人心碎”的点:你明明有八核十六线程的机器,Python却只能用一核?其实也不至于——你得用对方法。
Python有个老朋友叫 multiprocessing 模块,它不像 threading 那样共用一个解释器,而是直接开新进程,每个进程都是独立的Python解释器,天然绕开GIL。
你可以这么干:
from multiprocessing import Process
defcount():
x = 0
for _ in range(10 ** 7):
x += 1
processes = [Process(target=count) for _ in range(4)]
for p in processes:
p.start()
for p in processes:
p.join()
这回你会看到CPU呼呼转起来了,性能翻倍不止。唯一缺点就是多进程间通信成本略高,不如线程共享内存那么方便。
那是不是GIL就是个坏东西?
我觉得也不是,GIL的存在是历史原因 + 实用主义折中。CPython内部用了很多C语言写的原始数据结构,比如dict、list等,如果每个都要搞线程锁,性能还不如全部串行跑。
GIL反而简化了开发,在单线程I/O密集场景下依然强大好用。你只要认清楚它的特点,就知道怎么避坑。
面试遇到“线程和GIL”怎么回答最稳?
大厂喜欢考这个,因为它能一秒看出你是真搞过并发编程,还是只会复制粘贴。给你个我常用的回答模版:
面试官:Python多线程性能怎么样?你怎么看GIL?
我的回答:
Python的全局解释器锁(GIL)会导致在CPython中,多线程不能并行执行字节码,所以对于CPU密集型任务,比如数据处理、复杂算法,使用线程并不会带来性能提升,反而可能因为GIL争用变慢。 相比之下,对于I/O密集型任务,比如网络请求、文件读写,由于线程大部分时间在等待I/O,这种场景下Python线程仍然非常有用,可以提升整体响应速度。 如果需要并行处理CPU任务,我会选择
multiprocessing模块,绕过GIL的限制,充分利用多核。 GIL本质上是CPython对线程安全的一种简化方案,也有助于很多C扩展的稳定运行,不是缺陷,而是一种设计权衡。
这段话能让你稳稳立住:“我知道GIL的影响,也知道怎么绕过它”,直接打消面试官的疑虑。
要是你说:“我觉得Python线程性能不行”,那就太表面了,下一位。
你有遇到因为GIL踩坑的项目经验吗?我倒是有一个爬虫并发搞挂了的故事,哪天咱再细聊。