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 的协程模型仍是效率最优解。


关联