Python 并发与内存模型
Python 的并发由 GIL/free-threading 策略和 asyncio 协程双重驱动;内存通过引用计数 + 分代 GC 管理。理解这两者,才能真正理解 Python 程序的性能边界。
核心概念速览
| 概念 | 含义 |
|---|---|
| 并发 | 多任务交替执行,不要求同时(单核也可) |
| 并行 | 多任务同时执行,需要多核 |
| 同步 | 任务 A 等任务 B 完成后才继续 |
| 异步 | 任务 A 发起后不等待 B,继续做其他事 |
| 阻塞 | 等待资源时线程/进程暂停 |
| 非阻塞 | 等待资源时立即返回,不暂停 |
“并发是关于处理很多事情的能力,并行是关于同时做很多事情。” — Rob Pike
GIL 与 Free-threading
什么是 GIL
**GIL(Global Interpreter Lock)**是 CPython 解释器中的互斥锁,确保同一时刻只有一个线程执行 Python 字节码。它简化了 CPython 的内存管理,但也使多线程无法利用多核 CPU。
GIL 的影响
| 场景 | 受 GIL 影响? | 策略 |
|---|---|---|
| I/O 密集型 | 否 | 线程等待 I/O 时释放 GIL,多线程有效 |
| CPU 密集型 | 是 | 线程串行执行,应用 multiprocessing 替代 |
Free-threading(2026 年现状) 🔴 重要更新
Python 3.13 引入了实验性 free-threaded 构建(PEP 703),3.14 正式支持。 这意味着 GIL 已被移除,多线程可以真正并行执行。
| 版本 | 状态 |
|---|---|
| 3.13 | 实验性 free-threading(python3.13t) |
| 3.14 | 正式支持,独立 ABI 标签 cp314t |
| 3.14t | 生产可用,CPU 密集任务可获 3-10× 加速 |
当前建议:
- free-threading 是针对 CPU 密集多线程的 opt-in 能力,不是默认运行时
- I/O 密集型应用:asyncio 仍是主线方案
- 依赖生态:PyTorch 等已有实验性支持,但并非所有库兼容
t构建 - 中小团队:新项目可控依赖时可引入 3.14t 变体做 A/B benchmark
多线程
import threading
def worker():
print("工作线程")
t = threading.Thread(target=worker)
t.start()
t.join()适用:I/O 密集型(网络请求、文件读写)。CPU 密集型应使用 free-threaded 构建或多进程。
线程池(推荐):
from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=4) as executor:
results = executor.map(process, items)多进程
绕过 GIL 实现真正并行——每个进程有独立的 Python 解释器和 GIL。
from multiprocessing import Process
def compute(n):
return sum(range(n))
p = Process(target=compute, args=(10**7,))
p.start()
p.join()进程池(推荐):
from concurrent.futures import ProcessPoolExecutor
with ProcessPoolExecutor() as executor:
results = executor.map(compute, workloads)asyncio 异步编程
单线程事件循环驱动的协程并发——I/O 密集场景的高效方案。
基本用法
import asyncio
async def fetch(url):
# 模拟网络请求
await asyncio.sleep(1)
return f"data from {url}"
async def main():
results = await asyncio.gather(
fetch("url1"),
fetch("url2"),
fetch("url3"),
)
print(results)
asyncio.run(main())TaskGroup(Python 3.11+)— 结构化并发
async def main():
async with asyncio.TaskGroup() as tg:
tg.create_task(fetch("url1"))
tg.create_task(fetch("url2"))
tg.create_task(fetch("url3"))
# 所有任务自动等待完成;任一失败则取消其余TaskGroup 的核心优势:任务生命周期受控——退出 async with 块时,所有子任务自动等待和清理。
Trio / AnyIO — 结构化并发的下一步
- Trio:基于”nursery”范式的 async 库,让并发更可控
- AnyIO:多后端抽象层,兼容 asyncio 和 Trio,提升库的可移植性
同步原语
import threading
lock = threading.Lock()
with lock: # 推荐使用 with
# 临界区
shared_data.update()
# 信号量 — 限制并发数
sem = threading.Semaphore(5)
with sem:
access_limited_resource()
# 条件变量 — 线程等待/通知
condition = threading.Condition()
with condition:
while not data_ready:
condition.wait()
process(data)内存管理:引用计数
Python 的所有对象通过引用计数管理生命周期:
a = [1, 2, 3] # 引用计数 = 1
b = a # 引用计数 = 2
del a # 引用计数 = 1
b = None # 引用计数 = 0 → 对象被回收引用计数归零 → 对象立即销毁,内存释放。
循环引用与垃圾回收
引用计数无法处理循环引用:
class Node:
def __init__(self):
self.ref = None
a = Node()
b = Node()
a.ref = b # a → b
b.ref = a # b → a(循环引用!)
del a, b # 引用计数不归零 → 内存泄漏标记 - 清除算法
从根对象出发遍历所有可达对象,标记为”活动”。剩余未标记的是垃圾,予以清除。
分代回收
将对象按存活时间分为三代:
| 代 | 回收频率 | 特点 |
|---|---|---|
| 0 代 | 最高 | 新创建的对象,生命周期通常短 |
| 1 代 | 中等 | 经过一次 GC 存活的对象 |
| 2 代 | 最低 | 长期存活的对象 |
年轻代回收更频繁——“大多数对象朝生夕死”。
gc 模块
import gc
gc.collect() # 手动触发全代回收
print(gc.get_count()) # 各代待回收对象数
print(gc.get_threshold()) # GC 触发阈值
gc.set_threshold(700, 10, 10) # 调整阈值
# 调试循环引用
gc.set_debug(gc.DEBUG_SAVEALL) # 保留不可达对象以便检查
# 注册 GC 回调
def on_gc(phase, info):
print(f"GC phase: {phase}")
gc.callbacks.append(on_gc)关闭自动 GC(慎用):
gc.disable()
# ... 性能关键代码 ...
gc.enable()并发选型总结
| 场景 | 工具 | 备注 |
|---|---|---|
| I/O 密集型(网络/文件) | asyncio + TaskGroup | 首选,高并发低开销 |
| CPU 密集型(多核) | multiprocessing / 3.14t 线程 | 多进程稳定,free-threading 未来可期 |
| 混合场景 | concurrent.futures 双池 | 线程池处理 I/O,进程池处理计算 |
| 百万连接 | asyncio + uvloop / Tokio(Python 侧用 async) | Python 不适合极端高吞吐,边界交给 Go/Rust |
Python 3.14t 是分水岭——未来 CPU 密集型任务可以选择在 free-threaded 构建中直接使用多线程。但 I/O 密集型场景中,asyncio 的协程模型仍是效率最优解。
关联
- meta: 并发模型 — 跨语言并发哲学对比
- meta: 内存管理 — 引用计数与 GC 的设计权衡
- TypeScript: 并发与事件模型 — 事件循环对比