Python GIL全局解释器锁:到底是帮手还是绊脚石?

如果 Python 编程中使用了多线程,估计都听说过 GIL,全局解释器锁,Global Interpreter Lock
很多人一听这玩意儿,第一反应就是:这不就是 Python 最大的缺陷设计嘛!其实 GIL 并不是毫无用处,它有历史背景,也有优缺点。
今天我就来带你从零开始聊聊它,保证讲得接地气。
GIL 介绍
通俗涞水,GIL 就是一把 “全局的锁”,在任何时刻,只允许一个线程真正执行 Python 字节码
是不是感觉很离谱?多线程居然不能并行?
那为啥要设计这么个东西呢?
原因其实挺简单:Python(准确说是 CPython 解释器)在管理内存的时候,不是线程安全的。
如果没有锁,不同线程同时修改内存数据,很容易造成崩溃。所以 GIL 相当于一个“安全保护罩”,保证同一时刻只有一个线程在操作解释器内部的数据。
一句话总结:GIL 是 Python 为了简单省事搞出来的妥协方案。
特点
GIL 有几个特别容易被误解的特点,具体如下:
1、多线程不是真并行(CPU 密集型任务下)
比如:你开 10 个线程跑计算任务,结果可能还没单线程快,因为线程之间可能还要 “争夺”这把锁
2、I/O 密集型任务友好
读写文件、网络请求这种,线程会经常被阻塞,Python 会把 GIL 释放给别的线程。
所以这种场景,多线程还是能带来提升。
3、GIL 和多进程没关系
多进程每个进程都有自己的解释器和 GIL,互不干扰。针对 CPU 密集型任务,用多进程才是正解
实战
我们来写一个小例子:用多线程跑计算密集型任务(比如循环计算),再对比单线程。
import threading
import time
def cpu_task():
count = 0
for i in range(10**7):
count += i
return count
def run_threads():
threads = []
for _ in range(4): # 开 4 个线程
t = threading.Thread(target=cpu_task)
threads.append(t)
t.start()
for t in threads:
t.join()
if __name__ == "__main__":
start = time.time()
run_threads()
end = time.time()
print("多线程耗时:", end - start)
start = time.time()
for _ inrange(4):
cpu_task()
end = time.time()
print("单线程耗时:", end - start)运行后,我们发现:多线程并没有比单线程快,甚至更慢
原因就是所有线程都在抢 GIL,互相切换反而增加了开销。
如果换成 I/O 任务,比如:requests.get() 这种,多线程才会明显快一些
GIL 的应对策略
既然 GIL 有限制,那我们实际开发中怎么应对呢?
针对 I/O 密集型任务,使用用多线程
比如:爬虫、网络请求这类,使用 Python 多线程就很香
而针对 CPU 密集型任务,更建议用多进程
用 multiprocessing,或者干脆把耗时计算扔给 C 扩展、NumPy 这种底层释放 GIL 的库
总结
GIL 的本质是 为了让 CPython 简化内存管理,它让 Python 多线程在 CPU 密集型任务下“失色”,但在 I/O 密集型场景仍然有优势。而遇到 CPU 密集型任务时,不要硬刚多线程,直接上多进程或者调用 C 扩展库才是王道。
所以,下次再有人说 “Python 慢,是因为 GIL”,你完全可以这样回复:它仅是限制了 CPU 多线程,其他方面不会有影响!
更多推荐


所有评论(0)