一、GIL概述:什么是全局解释器锁
GIL(Global Interpreter Lock,全局解释器锁)是CPython解释器中的一个互斥锁,它确保同一时刻只有一个线程在执行Python字节码。这意味着即使在多核处理器上,标准的CPython程序也无法真正并行利用多个CPU核心来执行Python代码。
GIL的存在是CPython设计中的一个经典权衡——它极大地简化了内存管理(尤其是引用计数),但代价是牺牲了多线程的并行执行能力。理解GIL对于编写高效的Python并发程序至关重要。
核心定义:GIL是一个全局互斥锁,它强制在任意时刻只有一个线程可以执行Python字节码。这是CPython解释器最著名也最具争议的设计决策。
# 最直观的证明:两个线程分别执行无限循环
# 在双核CPU上,CPU使用率不会超过100%(单核满载)
import threading
import time
def busy_loop():
while True:
pass
t1 = threading.Thread(target=busy_loop)
t2 = threading.Thread(target=busy_loop)
t1.start()
t2.start()
# 观察任务管理器:CPU占用约等于单核100%,而非200%
# 这就是GIL在发挥作用
二、GIL存在的根本原因
2.1 引用计数的线程安全问题
CPython的内存管理主要依赖引用计数(Reference Counting)。每个Python对象都有一个引用计数变量,当引用计数变为0时,对象会被立即回收。如果允许多个线程同时操作同一对象的引用计数,就会产生竞态条件(Race Condition):两个线程同时读取引用计数,各自加1后再写回,导致引用计数只增加了1而不是2,最终可能造成对象被过早释放或内存泄漏。
# 引用计数竞态条件示意(纯理论演示,实际有GIL保护)
import sys
obj = []
print(sys.getrefcount(obj)) # 查看引用计数
# 如果没有GIL,两个线程同时执行以下操作会出问题:
# 线程A: ref_cnt = obj.ref_count # 读取到2
# 线程B: ref_cnt = obj.ref_count # 也读取到2
# 线程A: obj.ref_count = ref_cnt + 1 # 写入3
# 线程B: obj.ref_count = ref_cnt + 1 # 写入3(本该是4!)
关键洞察:GIL通过序列化所有Python字节码的执行,从根本上消除了引用计数的竞态条件。如果没有GIL,CPython需要为每个对象引入细粒度的锁,这会导致更高的开销和更复杂的实现。
2.2 CPython的其他非线程安全组件
除了引用计数,CPython解释器中还有许多内部数据结构也不是线程安全的:
- 全局对象缓存:如小整数缓存(-5到256的整数对象池)、空列表缓存等
- 内置类型的内存分配器:PyMalloc分配器并非线程安全
- 垃圾回收器:循环垃圾检测器(gc模块)的标记-清除算法需要独占访问
- 线程安全模块:某些标准库模块假设了单线程执行环境
为所有这些组件分别加锁不仅工作量巨大,而且锁竞争的开销可能远超GIL本身。因此,CPython的设计者选择了GIL这个"一刀切"的解决方案。
三、GIL的工作原理
3.1 线程调度与GIL获取
在CPython中,线程的执行遵循以下模式:
- 每个线程在执行任何Python字节码之前,必须先获取GIL
- 获取GIL的线程可以执行Python字节码
- 其他线程在等待GIL时被阻塞,处于睡眠状态
- GIL会在特定条件下被释放,允许其他线程执行
3.2 check_interval机制(Python 3.2之前)
在Python 3.2之前,GIL每执行100条字节码指令(通过sys.setcheckinterval()配置)就会被释放一次。这是一种基于指令计数的协作式调度:
# Python 2.x 中的GIL切换机制(概念性代码)
while True:
if --ticker <= 0: # ticker初始值为check_interval(默认100)
ticker = check_interval
release_gil()
acquire_gil()
execute_next_bytecode()
这种机制的缺点很明显:CPU密集型的线程会持续占用GIL直到检查点到达,导致IO密集型的线程得不到及时响应。
3.3 改进的GIL实现(Python 3.2+)
Python 3.2中,Antoine Pitrou重新设计了GIL实现(PEP 311),采用了基于超时的机制:
- 当前持有GIL的线程有一个"超时计数器"
- 其他线程在等待GIL时会设置一个等待超时(默认5毫秒)
- 如果当前线程在超时时间内没有主动释放GIL,操作系统会发送信号通知当前线程释放GIL
- 当前线程在安全点(通常是下一个字节码边界)检查并释放GIL
# Python 3.2+ 基于超时的GIL调度(概念示意)
# 当前线程执行循环:
while True:
if gil_dropped_signal_received():
release_gil()
gil_dropped = False
acquire_gil() # 可能自己重新获取,也可能让给其他线程
execute_next_bytecode()
# 等待线程:
# 1. 设置5ms定时器
# 2. 如果定时器到期仍未获取到GIL,发送信号给当前线程
# 3. 当前线程在下一个安全点检查信号并释放GIL
改进效果:新机制将GIL切换延迟从大约100条字节码指令的不可预测时间降低到大约5毫秒的上限,显著改善了IO密集型任务的响应性。同时,新机制在多核CPU上的表现也更加稳定。
3.4 GIL的释放时机
即使持有GIL,线程在以下情况下也会主动释放它:
- IO操作:所有阻塞式IO操作(读文件、网络请求等)在等待期间都会释放GIL
- 时间计算:
time.sleep()等函数会释放GIL
- C扩展调用:许多C扩展函数(如NumPy的矩阵运算)在执行耗时计算时会主动释放GIL
- 字节码间隔:每执行一定数量的字节码指令后会检查是否需要释放
# IO操作会释放GIL——验证实验
import threading
import time
def cpu_intensive():
# CPU密集:纯Python计算,不释放GIL
count = 0
for i in range(10 ** 7):
count += i * i
print(f"CPU done: {count}")
def io_simulated():
# IO模拟:time.sleep会释放GIL
for i in range(5):
time.sleep(0.1) # 释放GIL,让CPU线程执行
print("IO done")
t1 = threading.Thread(target=cpu_intensive)
t2 = threading.Thread(target=io_simulated)
start = time.time()
t1.start(); t2.start()
t1.join(); t2.join()
print(f"Total time: {time.time() - start:.2f}s")
四、GIL对并发性能的影响实测
4.1 CPU密集型任务:多线程 vs 单线程 vs 多进程
对于CPU密集型的任务(如大量数学计算、数据处理),多线程不仅没有加速,反而因为GIL的竞争和线程切换开销,性能可能比单线程还要差。真正能利用多核的是多进程(multiprocessing)。
# CPU密集型任务对比实验
import threading
import multiprocessing
import time
def count_down(n):
while n > 0:
n -= 1
N = 10 ** 8
# 1. 单线程基准
start = time.time()
count_down(N)
print(f"单线程: {time.time() - start:.2f}s")
# 2. 多线程(受GIL限制)
t1 = threading.Thread(target=count_down, args=(N//2,))
t2 = threading.Thread(target=count_down, args=(N//2,))
start = time.time()
t1.start(); t2.start()
t1.join(); t2.join()
print(f"多线程: {time.time() - start:.2f}s") # 通常 > 单线程时间
# 3. 多进程(绕过GIL)
p1 = multiprocessing.Process(target=count_down, args=(N//2,))
p2 = multiprocessing.Process(target=count_down, args=(N//2,))
start = time.time()
p1.start(); p2.start()
p1.join(); p2.join()
print(f"多进程: {time.time() - start:.2f}s") # 接近单线程的一半(理想情况)
4.2 IO密集型任务:多线程展现优势
对于IO密集型的任务(如网络爬虫、文件读写、数据库查询),多线程可以显著提升性能。因为线程在等待IO时会释放GIL,其他线程可以继续执行,从而实现并发效果。
# IO密集型任务对比实验
import threading
import multiprocessing
import time
import urllib.request
URLS = [
"https://www.example.com",
"https://www.python.org",
"https://www.github.com",
"https://stackoverflow.com",
"https://www.wikipedia.org",
] * 4 # 20个请求
def fetch_url(url):
try:
urllib.request.urlopen(url, timeout=5)
except:
pass
# 1. 串行执行
start = time.time()
for url in URLS:
fetch_url(url)
print(f"串行: {time.time() - start:.2f}s")
# 2. 多线程并发
threads = []
start = time.time()
for url in URLS:
t = threading.Thread(target=fetch_url, args=(url,))
threads.append(t)
t.start()
for t in threads:
t.join()
print(f"多线程: {time.time() - start:.2f}s") # 显著快于串行
| 任务类型 |
串行执行 |
多线程 |
多进程 |
结论 |
| CPU密集(纯计算) |
1.0x(基准) |
0.8x - 1.0x(更差或持平) |
N倍(N≈核心数) |
CPU密集用多进程 |
| IO密集(网络/磁盘) |
1.0x(基准) |
3x - 10x+(大幅提升) |
3x - 10x+(类似) |
IO密集用多线程(更轻量) |
| 混合型 |
1.0x(基准) |
中等提升 |
较大提升 |
视具体比例而定 |
五、绕过GIL的方案
5.1 multiprocessing:多进程方案
multiprocessing模块通过创建子进程而非子线程来绕过GIL。每个进程拥有独立的Python解释器和内存空间,因此有自己独立的GIL,可以真正并行执行在多核CPU上。
# multiprocessing 基本用法
from multiprocessing import Pool, cpu_count
import time
def is_prime(n):
if n < 2:
return False
for i in range(2, int(n ** 0.5) + 1):
if n % i == 0:
return False
return True
numbers = [15485863, 15485867, 15485869, 15485917]
# 串行处理
start = time.time()
results = [is_prime(n) for n in numbers]
print(f"串行: {time.time() - start:.3f}s - {results}")
# 多进程并行(利用所有CPU核心)
with Pool(processes=cpu_count()) as pool:
start = time.time()
results = pool.map(is_prime, numbers)
print(f"多进程: {time.time() - start:.3f}s - {results}")
注意事项:多进程的代价是更高的内存消耗和进程间通信(IPC)开销。使用multiprocessing时,数据需要通过pickle序列化后在进程间传递,对于大数据量可能成为瓶颈。此外,进程启动时间远大于线程启动时间。
5.2 使用C扩展释放GIL
C扩展可以在执行耗时计算时主动释放GIL,让其他Python线程得以执行。Python的C API提供了Py_BEGIN_ALLOW_THREADS和Py_END_ALLOW_THREADS宏来实现这一点。
// C扩展中释放GIL的示例(cext.c)
// 编译: gcc -shared -fPIC -I/usr/include/python3.10 -o cext.so cext.c -lpthread
// Py_BEGIN_ALLOW_THREADS 和 Py_END_ALLOW_THREADS 宏
#include <Python.h>
static PyObject *
heavy_compute(PyObject *self, PyObject *args) {
long n;
if (!PyArg_ParseTuple(args, "l", &n))
return NULL;
// 释放GIL——其他Python线程现在可以运行
Py_BEGIN_ALLOW_THREADS
// 执行耗时计算(此时不持有GIL)
long result = 0;
for (long i = 0; i < n; i++) {
result += i * i;
}
// 重新获取GIL,准备返回Python层
Py_END_ALLOW_THREADS
return PyLong_FromLong(result);
}
典型的利用了GIL释放机制的Python库:
- NumPy:数组运算在底层C代码中释放GIL,所以多线程调用NumPy运算可以并行
- Pandas:基于NumPy,同样在底层释放GIL
- OpenCV:图像处理函数在C++层释放GIL
# NumPy在C层释放GIL,因此多线程可并行
import numpy as np
import threading
import time
N = 5000
a = np.random.rand(N, N)
b = np.random.rand(N, N)
def matmul():
for _ in range(5):
np.dot(a, b)
# 多线程执行矩阵乘法——实际上可以并行!
# 因为NumPy的dot()在C层释放了GIL
threads = [threading.Thread(target=matmul) for _ in range(4)]
start = time.time()
for t in threads: t.start()
for t in threads: t.join()
print(f"NumPy多线程矩阵乘法: {time.time() - start:.2f}s")
5.3 Numba的JIT编译方案
Numba是一个JIT(Just-In-Time)编译器,它可以将Python函数编译为机器码。在Numba的nopython模式下编译的函数会完全绕过Python解释器,因此也不受GIL限制。如果函数中不包含Python对象操作,Numba可以自动释放GIL。
# Numba JIT编译绕过GIL
from numba import njit, prange
import threading
import time
# 使用nopython模式编译,自动处理GIL
@njit(nogil=True)
def numba_sum(n):
total = 0
for i in range(n):
total += i * i
return total
# 多线程调用Numba编译函数——可真正并行
def worker():
result = numba_sum(10 ** 8)
return result
# 预热Numba JIT
numba_sum(10)
threads = [threading.Thread(target=worker) for _ in range(4)]
start = time.time()
for t in threads: t.start()
for t in threads: t.join()
print(f"Numba多线程 (nogil=True): {time.time() - start:.2f}s")
5.4 ctypes调用外部C库
使用ctypes调用外部C函数时,Python会在调用期间释放GIL。这允许在调用C函数的同时,其他Python线程可以并行执行。
# 使用ctypes调用C标准库的qsort,调用期间释放GIL
import ctypes
import threading
import time
# 加载C标准库
libc = ctypes.CDLL("msvcrt.dll") # Windows
# libc = ctypes.CDLL("libc.so.6") # Linux
# 获取C库中的clock_gettime函数来验证
# ctypes 调用会自动释放GIL
def c_qsort_work():
# 准备数据
arr = (ctypes.c_int * 10000000)()
for i in range(10000000):
arr[i] = 10000000 - i
# qsort调用期间GIL被释放
libc.qsort(arr, len(arr), ctypes.sizeof(ctypes.c_int),
0) # 简化,实际需要比较函数指针
# 注意:ctypes调用外部函数时,GIL在调用期间被释放
# 这意味着在C函数执行期间,其他Python线程可以运行
5.5 asyncio:协程方案
严格来说,asyncio并不算绕过GIL,它是在单线程内实现了协作式多任务调度。协程在遇到await时主动让出控制权,让事件循环调度其他协程执行。这在IO密集型场景中可以达到极高的并发度。
# asyncio实现高并发IO
import asyncio
import time
async def fetch_url_async(url):
# 使用aiohttp或内置的asyncio HTTP客户端
# await asyncio.sleep模拟IO等待
await asyncio.sleep(0.1)
return url
async def main():
tasks = [fetch_url_async(f"https://example.com/{i}")
for i in range(1000)]
results = await asyncio.gather(*tasks)
print(f"完成 {len(results)} 个请求")
start = time.time()
asyncio.run(main())
print(f"asyncio耗时: {time.time() - start:.2f}s")
六、GIL消除的尝试与未来
6.1 PyPy的STM(Software Transactional Memory)
PyPy曾经尝试过使用STM(软件事务性内存)来消除GIL。STM的基本思想是:每个线程在自己的事务中执行,事务提交时自动检测冲突并回滚。PyPy的STM实现允许线程真正并行执行Python代码,但在实践中遇到了严重的性能问题——STM事务的开销巨大,导致即使在简单的基准测试中,单线程性能也比CPython的GIL版本慢得多。最终,PyPy的STM版本被放弃。
6.2 Jython和IronPython:无GIL实现
Jython(基于Java虚拟机)和IronPython(基于.NET CLR)分别依赖宿主运行时的线程模型,天然没有GIL限制。然而,它们对CPython标准库的兼容性始终不够好(尤其是C扩展完全无法使用),导致市场份额极小。
6.3 no-gil分支实验
社区中曾出现过多项去除GIL的实验性分支,最著名的包括:
- GILectomy:2013年的一个实验项目,从CPython中移除GIL,但导致性能下降严重
- Python GIL Removal的多个GSOC项目:多位学生在Google Summer of Code中尝试移除GIL,但都没有达到生产可用标准
- Larry Hastings的尝试:在2015年左右尝试移除GIL,同样因为单线程性能损失过大而放弃
核心矛盾:移除GIL面临的根本问题是:没有GIL就需要为对象引用计数加细粒度锁,这会使每个Python对象操作变慢(估算约30-50%的性能损失)。对于绝大多数单线程程序来说,这是一种无法接受的退化。因此,GIL的移除必须在不显著降低单线程性能的前提下完成——这是一个极其困难的工程挑战。
6.4 PEP 703:Free-Threaded Python
PEP 703("Making the Global Interpreter Lock Optional in CPython")由Sam Gross提出,是迄今为止最严肃、最可行的GIL移除方案。该提案的核心思路:
- 使GIL可选:通过在编译时传递
--disable-gil标志来构建无GIL的CPython
- 每对象锁:引入偏向锁(biased locking)技术,大多数对象操作仍然是原子的,只有在检测到竞争时才升级为真正锁
- 延迟引用计数(Deferred Reference Counting):减少引用计数操作频率,降低锁竞争
- Immortal对象:某些永不销毁的对象(如小整数、None、True、False)标记为"不朽",完全跳过引用计数
- 兼容性层:无GIL的CPython仍然可以运行绝大多数现有代码,C扩展需要适配
# 体验Free-Threaded Python(Python 3.13+)
# 安装: python3.13 -m pip install python3.13-nogil
# 运行: PYTHON_GIL=0 python script.py
import sys
import threading
import time
# 检查GIL是否被禁用
print(f"GIL enabled: {sys._is_gil_enabled()}")
def cpu_work(n):
total = 0
for i in range(n):
total += i * i
return total
N = 5 * 10 ** 7
threads = [threading.Thread(target=cpu_work, args=(N,))
for _ in range(4)]
start = time.time()
for t in threads: t.start()
for t in threads: t.join()
print(f"Free-threaded 多线程: {time.time() - start:.2f}s")
# 在无GIL模式下,这将近似利用4个CPU核心
6.5 PEP 703的时间线
| Python版本 |
状态 |
说明 |
| 3.12 |
实验性规划 |
PEP 703被接受,开始实现 |
| 3.13 |
实验性支持 |
--disable-gil构建选项可用,free-threaded模式首次公开 |
| 3.14 |
改进中 |
free-threaded性能优化,C扩展兼容性增强 |
| 3.15+ |
目标稳定 |
计划使free-threaded模式达到生产可用水平 |
七、何时选择多线程 vs 多进程 vs asyncio
7.1 决策树
使用多线程 (threading)
- IO密集型任务(网络请求、文件操作、数据库查询)
- 需要共享大量数据
- 任务数量适中(几十到几百)
- 需要低延迟响应
- 需要保留调用栈信息(调试友好)
使用多进程 (multiprocessing)
- CPU密集型任务(计算、数据处理)
- 需要真正并行利用多核
- 任务之间数据耦合度低
- 可以接受较高的内存开销
- 需要隔离性(一个进程崩溃不影响其他)
使用 asyncio
- 高并发IO(成千上万个连接)
- 任务主要是等待IO
- 需要极轻量级的"任务"(比线程更轻)
- 有现成的异步库支持
- 需要精确的控制流和取消语义
使用 concurrent.futures
- 需要在不同并发模式间灵活切换
- 有现成的线程池/进程池需求
- 需要统一的Future接口
- 方便在同步和异步之间桥接
7.2 综合决策建议
# 实际开发中的选择指南
# 场景1:网络爬虫(IO密集,数百请求)-> 多线程或asyncio
# 选择asyncio(如果可用异步库)或threading(如果有同步库依赖)
# 场景2:图像批量处理(CPU密集)-> 多进程
from multiprocessing import Pool
with Pool() as pool:
results = pool.map(process_image, image_files)
# 场景3:Web服务器(混合型)-> asyncio + 多进程
# 使用gunicorn或uvicorn的多worker模式
# 每个worker是独立进程,内部使用asyncio处理请求
# 场景4:GUI应用(事件驱动)-> 主线程 + 工作线程
# 主线程处理GUI事件,工作线程执行耗时操作
import tkinter as tk
from threading import Thread
def long_task():
# 执行耗时操作,不阻塞GUI
result = expensive_computation()
# 通过queue或回调传回结果
root.after(0, lambda: update_gui(result))
root = tk.Tk()
Thread(target=long_task, daemon=True).start()
root.mainloop()
八、GIL内部实现深入
8.1 从Python源码看GIL
CPython的GIL实现在Python/ceval_gil.c文件中。核心数据结构如下:
// CPython源码中GIL的核心结构(简化)
// 文件: Python/ceval_gil.c
struct _gil {
// 最后一个持有GIL的线程ID
unsigned long last_holder;
// 条件变量,用于线程等待GIL
PyCOND_T cond;
// 互斥锁,保护GIL状态
PyMUTEX_T mutex;
// GIL是否被持有
int locked;
};
// GIL的获取操作(简化伪代码)
void take_gil(PyThreadState *tstate) {
int err = 0;
PyMUTEX_LOCK(gil.mutex);
while (gil.locked) {
// 计算超时时间(默认5ms)
// 等待条件变量
err = PyCOND_WAIT(gil.cond, gil.mutex, SWITCH_INTERVAL);
if (err == ETIMEDOUT) {
// 超时,强制当前持有者释放GIL
drop_gil(0);
break;
}
}
gil.locked = 1;
gil.last_holder = tstate->thread_id;
PyMUTEX_UNLOCK(gil.mutex);
}
// GIL的释放操作(简化伪代码)
void drop_gil(int Py_UNUSED(not_used)) {
if (!gil.locked)
return;
gil.locked = 0;
gil.last_holder = 0;
// 通知等待线程
PyCOND_SIGNAL(gil.cond);
}
8.2 sys._current_frames()调试技巧
# 调试GIL竞争和线程状态的实用技巧
import sys
import threading
import time
import traceback
def debug_threads():
"""打印当前所有线程的调用栈"""
for thread_id, frame in sys._current_frames().items():
print(f"\n--- 线程 {thread_id} ---")
traceback.print_stack(frame)
# 在另一个线程中定时调用debug_threads()
# 可以帮助诊断GIL竞争导致的问题
# 查看当前GIL切换间隔(Python 3.12+)
import sys
# sys.getswitchinterval() 返回GIL切换间隔(秒)
print(f"当前GIL切换间隔: {sys.getswitchinterval()}秒")
# 调整GIL切换间隔(谨慎使用)
sys.setswitchinterval(0.001) # 1ms,更频繁的切换
九、常见陷阱与最佳实践
9.1 GIL相关的常见误解
误解1:"GIL意味着Python程序不能用多核" —— 实际上,多进程、C扩展释放GIL、NumPy等方案都可以利用多核,只是纯Python多线程不能
误解2:"GIL让线程完全没用" —— IO密集型任务中多线程仍然非常有效,因为IO等待期间GIL被释放
误解3:"GIL保证线程安全" —— GIL只保证单个字节码指令的原子性,不保证复合操作的线程安全(如if key in dict: dict[key] += 1)
9.2 即使有GIL也需要锁的场景
# 有GIL仍然需要锁的例子
import threading
counter = 0
lock = threading.Lock()
def unsafe_increment():
global counter
for _ in range(100000):
# 即使有GIL,这一行也不是线程安全的!
# 因为 counter += 1 对应三条字节码:
# 1. LOAD counter 2. ADD 1 3. STORE counter
counter += 1
def safe_increment():
global counter
for _ in range(100000):
with lock:
counter += 1
# 验证结果差异
threads = [threading.Thread(target=unsafe_increment) for _ in range(10)]
for t in threads: t.start()
for t in threads: t.join()
print(f"无锁结果: {counter}(应该是 1000000,但通常更少)")
9.3 性能分析工具
# 使用profile工具分析GIL竞争
# pip install gil_load
import gil_load
import threading
import time
gil_load.init()
@gil_load.profile
def run_workers():
def work():
total = 0
for i in range(10 ** 7):
total += i ** 2
return total
threads = [threading.Thread(target=work) for _ in range(4)]
for t in threads: t.start()
for t in threads: t.join()
run_workers()
# gil_load会输出GIL等待时间的统计信息
# 帮助判断GIL是否成为性能瓶颈
十、核心要点总结
1. GIL的本质:CPython中的一个全局互斥锁,确保同一时刻只有一个线程执行Python字节码。它简化了内存管理但限制了并行性。
2. GIL为什么存在:主要为了保护引用计数的线程安全。没有GIL,需要为每个Python对象加锁,这将导致巨大的性能开销和实现复杂性。
3. GIL的工作原理:Python 3.2+使用基于超时的调度机制,默认5ms切换间隔。IO操作会主动释放GIL,CPU密集型操作则持续持有。
4. IO密集 vs CPU密集:多线程对IO密集型任务有效(IO时释放GIL),但对CPU密集型任务无效甚至更差。
5. 绕过GIL的四大方案:①multiprocessing多进程 ②使用释放GIL的C扩展(NumPy等)③Numba JIT编译(nogil=True)④ctypes调用C函数。
6. GIL的未来:PEP 703(free-threaded Python)正在使GIL变为可选。Python 3.13已提供实验性支持,预计3.15+达到生产可用水平。
7. 并发选型原则:CPU密集用多进程,IO密集用多线程或asyncio,混合型用多进程+asyncio组合。
8. 即使有GIL也需要锁:GIL不保证复合操作的原子性,非原子操作(如counter += 1)仍需要显式加锁。
进一步学习建议:
- 阅读CPython源码
Python/ceval_gil.c了解GIL的精确实现
- 使用
gil_load等工具分析自己项目的GIL竞争情况
- 关注PEP 703的进展,free-threaded Python可能会改变Python并发编程的格局
- 深入学习
asyncio的设计模式——在很多场景下,它是比多线程更优雅的并发方案