系列:Python 底层设计逻辑|类型:原理拆解型|分发:CSDN / 博客园 / 腾讯云 字数:约 1600 字
结论先行:GIL(全局解释器锁)是 CPython 里一把"全局锁",保证同一时刻只有一个线程在跑 Python 字节码。它锁的是"解释器执行权",不是你的变量。所以 CPU 密集型任务用多线程基本白搭——多个线程轮流抢这把锁,反而有切换开销;但 I/O 密集型任务(网络、文件)不受影响,因为等待时会释放 GIL。想真并行算,得上多进程或把计算丢给 C 扩展。
很多人以为 GIL 是防止多线程改同一个变量。错。保护变量靠的是你自己的锁(threading.Lock)。GIL 锁的是解释器本身——CPython 的内存管理(引用计数)不是线程安全的,所以干脆让同一时刻只有一个线程能执行字节码,从根本上避开竞争。
import threading
x = 0
def inc():
global x
for _ in range(100000):
x += 1
# 两个线程各加 10 万次,结果却常常不是 200000关键:上例结果不对,是因为 x += 1 不是原子操作,GIL 会在中间切换——这跟 GIL"防竞争"没关系,恰恰说明 GIL 不保护你的数据,你得自己加锁。
import time, threading, multiprocessing
def cpu_task():
n = 0
for _ in range(10_000_000):
n += 1
# 多线程
t0 = time.time()
ts = [threading.Thread(target=cpu_task) for _ in range(4)]
[t.start() for t in ts]; [t.join() for t in ts]
print("线程:", time.time() - t0)
# 多进程
t0 = time.time()
ps = [multiprocessing.Process(target=cpu_task) for _ in range(4)]
[p.start() for p in ps]; [p.join() for p in ps]
print("进程:", time.time() - t0)现象:四核机器上,多进程明显比多线程快接近一倍,多线程几乎没加速。原因就是 GIL——四个线程挤一把锁,谁也别想真并行算。
import time, threading, requests
def io_task():
requests.get("https://www.baidu.com") # 网络等待
t0 = time.time()
ts = [threading.Thread(target=io_task) for _ in range(10)]
[t.start() for t in ts]; [t.join() for t in ts]
print("IO 线程:", time.time() - t0)为什么:网络请求时线程在等,CPython 会在等待点释放 GIL,别的线程就能跑。所以 I/O 密集场景多线程完全能打,这也是 Web 服务爱用多线程的原因。
Python 3.2 之后 GIL 改用"超时切换":一个线程默认跑 5 毫秒就主动让出,避免单线程霸占。你可以用 sys.setswitchinterval 调这个值观察行为变化。但别指望它让 CPU 任务变快——切换更公平,不等于能并行。
multiprocessing 开多进程,每个进程独立 GIL、独立解释器,真并行。(代价是进程间通信贵。)asyncio:单线程并发处理海量 I/O,本就不依赖多线程。场景 | 多线程有效吗 | 原因 |
|---|---|---|
纯计算(循环/数学) | ❌ 几乎无效 | 抢同一把 GIL,无法并行 |
网络/文件 I/O | ✅ 有效 | 等待时释放 GIL |
NumPy 向量化计算 | ✅ 有效 | 底层 C 释放 GIL |
多进程 | ✅ 真并行 | 每进程一把独立 GIL |
Lock 还得加。一句话记住:GIL 锁的是解释器的"执行权",不是你的数据。CPU 活儿上进程,I/O 活儿随便多线程——先分清瓶颈在哪,再决定怎么绕。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。