2026 版 Python GIL 全解:free-threaded CPython 下,线程安全到底该怎么写?
如果你已经学习或使用 Python 多线程一段时间,大概反复听过一句话:
“CPython 有 GIL,所以同一时刻只有一个线程执行 Python 代码。”
过去,这句话确实是理解 Python 并发的重要入口。但到了 2026 年,如果还只靠这一句话判断线程安全,就很容易得出错误结论。
Python 3.13 首次提供实验性的 free-threaded CPython;进入 Python 3.14 后,PEP 779 又把它推进到了“正式支持,但仍然可选”的阶段。换句话说,我们已经处在一个很有意思的过渡时期:传统带 GIL 的 CPython 仍然是大量项目的现实环境,而禁用 GIL 的 CPython 已经不能再被当成遥远的实验品。(peps.python.org)
于是,下面这段再普通不过的代码,值得重新认真看一遍:
counter = 0
def worker():
global counter
for _ in range(1_000_000):
counter += 1
如果两个线程同时执行 worker():
import threading
counter = 0
def worker():
global counter
for _ in range(1_000_000):
counter += 1
threads = [
threading.Thread(target=worker)
for _ in range(2)
]
for t in threads:
t.start()
for t in threads:
t.join()
print(counter)
我们当然希望最后得到:
2000000
但真正值得追问的不是“跑出来是不是 2000000”,而是:
在传统 CPython 中,我们应该怎样理解这段代码?在 free-threaded CPython 中,又应该怎样理解?
进一步往下追,你会碰到几个 2026 年 Python 并发编程绕不开的问题:
- 没有 GIL,是不是意味着 Python 自动线程安全?
dict、list内部已经有锁,我们还能不能依赖“原子操作”?- 为什么一个 C extension 甚至可能让已经禁用的 GIL 重新开启?
- 程序怎样判断当前解释器到底有没有启用 GIL?
下面就从这一个 counter += 1 开始,把它们逐个拆清楚。
一、先把 GIL 放回正确的位置:它到底保护什么?
GIL,全称:
Global Interpreter Lock
也就是全局解释器锁。
在传统 CPython 中,一个进程完全可以创建多个线程:
Python Process
├── Thread A
├── Thread B
├── Thread C
└── Thread D
但真正执行 Python 代码时,大致可以理解为:
GIL
│
┌───────┴───────┐
│ │
Thread A 执行 其他线程等待
│
释放/切换
│
Thread B 执行
因此,对于 CPU 密集型纯 Python 代码:
def cpu_task():
total = 0
for i in range(100_000_000):
total += i * i
单纯创建 4 个线程,通常并不能让 4 个 CPU 核心同时跑这几段 Python 代码。
但这里特别容易冒出一个误区:
GIL 存在,不等于你的 Python 程序天然线程安全。
Python 官方文档同样指出,即使解释器启用了 GIL,纯 Python 多线程程序在共享状态上仍然需要同步。(docs.python.org)
为什么?
因为:
counter += 1
从你的业务视角看像“一件事”,但从解释器执行和共享状态的角度看,它并不是一个不可分割的事务。
二、counter += 1 看起来只有一行,为什么仍然危险?
表面上:
counter += 1
确实只有一行。
但从语义上拆开,至少可以理解成:
读取 counter
↓
计算 counter + 1
↓
产生新的 int
↓
把结果重新绑定给 counter
也就是:
old = counter
new = old + 1
counter = new
真正需要警惕的是这三个阶段:
Read → Modify → Write
这就是经典的 Read-Modify-Write Race。
假设两个线程都看到:
counter = 100
可能发生:
Thread A Thread B
读取 100
读取 100
计算 101
计算 101
写入 101
写入 101
两次 +1 都执行了,但最终结果只是:
101
而不是:
102
这就是常说的 lost update,更新丢失。
这里最好建立一个非常牢靠的判断习惯:
一行 Python 语句,不等于一个并发意义上的原子事务。
同样,也别把线程安全建立在某个 Python 版本当前的字节码布局上。CPython 的 specialization、解释器优化和未来实现都可能调整具体执行路径;真正应该依赖的是同步语义,而不是“我看这几条 opcode 好像不会被打断”。
三、传统 CPython 有 GIL,为什么还是不能把它当锁用?
回到:
counter += 1
传统 CPython 有 GIL,所以两个线程不会在同一时刻真正执行 Python 字节码。
于是很容易得出:
有 GIL
↓
同一时刻只有一个线程执行
↓
counter += 1 一定安全
问题恰恰出在最后一步。
更准确的理解应该是:
GIL
↓
解释器执行被全局串行化
↓
但一个业务操作可能跨越多个解释器步骤
↓
线程仍可能在操作边界之间发生切换
↓
所以不能把 GIL 当成应用层同步机制
麻烦的是,在某些 Python 版本、某些表达式以及某些调度条件下,你可能连续跑很多次,都看到:
2000000
这时候非常容易产生一种错觉:
“看起来没丢数据,所以应该是线程安全的。”
但并发代码里,测试没有撞上 race condition,并不能证明 race condition 不存在。
因此,即使你的程序目前只运行在传统 CPython 上,我仍然不会建议把:
counter += 1
当成共享状态的同步方案。
只要这个 counter 是多个线程共同修改的业务状态,就应该把同步策略写清楚。
四、free-threaded Python 真正改变了什么?
传统模式可以粗略画成:
CPU Core 1 CPU Core 2
Thread A
执行 Python
Thread B 等待
free-threaded CPython 的目标则更接近:
CPU Core 1 CPU Core 2
Thread A Thread B
Python Python
↓ ↓
真正并行执行
Python 官方文档说明,free-threaded 构建允许 Python 线程在多个 CPU 核心上并行运行,让设计得当的多线程程序能够真正利用多核 CPU。(docs.python.org)
于是前面的竞争条件就不再只是“线程切换时机”的问题,而会变成非常直接的并行读写:
counter = 100
Core 1 / Thread A Core 2 / Thread B
load 100 load 100
+ 1 + 1
store 101 store 101
所以到了 free-threaded CPython,过去那种:
“反正 GIL 最后会帮我兜住。”
的思路就不能再继续用了。
五、最直接的正确写法:给共享状态加锁
对于这个例子,最清楚的解决方式就是:
import threading
counter = 0
counter_lock = threading.Lock()
def worker():
global counter
for _ in range(1_000_000):
with counter_lock:
counter += 1
现在临界区就非常明确:
counter_lock
│
┌─────┴─────┐
│ │
Thread A Thread B
counter += 1 等待
无论当前环境是:
传统 CPython + GIL
还是:
free-threaded CPython
代码表达出来的并发语义都是一致的。
这也是更可靠的 Python 最佳实践:你的正确性应该来自代码显式表达的同步规则,而不是某个解释器实现“顺便”提供的保护。
六、每次 +1 都加锁,会不会又太慢?
会有这个问题。
如果直接写:
for _ in range(1_000_000):
with lock:
counter += 1
等于进行了 100 万次锁操作。
如果业务允许,更好的方式往往是先把工作放在线程局部状态里:
import threading
counter = 0
counter_lock = threading.Lock()
def worker():
global counter
local_counter = 0
for _ in range(1_000_000):
local_counter += 1
with counter_lock:
counter += local_counter
原来:
1,000,000 次共享写入
1,000,000 次加锁
现在变成:
1,000,000 次线程局部计算
+
1 次共享写入
1 次加锁
这个例子背后的原则,比“选哪一种 Lock”更重要:
能不共享,就尽量别共享;必须共享时,再缩小共享范围。
高性能并发程序通常不是靠“疯狂优化锁”赢的,而是靠:
怎样让线程尽可能少共享可变数据?
七、“没有 GIL”是不是意味着 Python 自动线程安全?
答案很明确:
不是。
free-threaded CPython 主要解决的是:
多个线程能不能同时安全地运行 CPython 解释器。
它没有替你解决:
多个线程修改同一份业务状态时,业务不变量能不能保持成立。
这两个层次一定要分开。
例如一个银行账户:
class Account:
def __init__(self, balance):
self.balance = balance
def withdraw(self, amount):
if self.balance >= amount:
self.balance -= amount
return True
return False
两个线程同时执行:
account.withdraw(800)
如果:
balance = 1000
完全可能出现:
Thread A Thread B
检查余额 1000 检查余额 1000
1000 >= 800 1000 >= 800
扣款 扣款
这里需要保护的并不是某一次属性读取或属性写入,而是:
检查余额
+
修改余额
这个完整的业务操作。
正确写法应该把这个不变量包在同一个临界区里:
import threading
class Account:
def __init__(self, balance):
self.balance = balance
self._lock = threading.Lock()
def withdraw(self, amount):
with self._lock:
if self.balance < amount:
return False
self.balance -= amount
return True
所以,在 free-threaded Python 下尤其要记住:
Interpreter Safety
≠
Application Thread Safety
解释器不会因为取消 GIL,就突然理解“余额不能被超额扣减”“库存不能减成负数”或者“同一订单只能处理一次”这些业务规则。
八、dict 已经有内部锁了,为什么我还要关心线程安全?
这正是 2026 年讨论 free-threaded Python 时特别容易混淆的一点。
为了在没有全局 GIL 的情况下保证解释器自身安全,CPython 给很多内置容器引入了更细粒度的同步方案。
例如 dict、list、set 会通过对象级锁、critical section,以及部分乐观访问机制保护自身结构。PEP 703 对这一设计有很详细的说明。(peps.python.org)
Python 3.14 的线程安全文档也已经明确列出了一些操作的保证。
例如普通 dict 的这些读取:
d[key]
d.get(key)
key in d
len(d)
具有相应的单次访问安全性;单个键的写入、删除、pop()、setdefault() 等操作也有内部同步保护。(docs.python.org)
但千万别因此继续推导:
“既然 dict 自己会加锁,那共享 dict 就不需要
threading.Lock了。”
这仍然是把“容器安全”和“业务事务安全”混到了一起。
九、内部锁保护的是 dict 自己,不是你的复合逻辑
看一个特别典型的计数器:
counts = {}
def increment(key):
counts[key] = counts.get(key, 0) + 1
其中:
counts.get(key, 0)
单独执行,可以安全地完成一次读取。
而:
counts[key] = value
单次写入也不会把字典内部结构写坏。
问题在于,它们组合起来以后变成了:
读取
↓
计算
↓
写回
于是两个线程仍然可能:
counts["python"] = 10
Thread A Thread B
get → 10 get → 10
+1 → 11 +1 → 11
write 11 write 11
最后结果是:
11
而不是:
12
Python 3.14 官方线程安全文档甚至直接把:
d[key] = d[key] + 1
列为 NOT atomic:read-modify-write;先检查再操作的 TOCTOU 写法同样不是原子的。(docs.python.org)
所以更准确的一句话应该是:
dict的内部同步负责避免容器自身被并发操作破坏,但它不会把你写出来的多步骤业务逻辑自动升级成事务。
十、另一个高频陷阱:Check-Then-Act
比如缓存初始化:
if user_id not in cache:
cache[user_id] = load_user(user_id)
单线程里很自然。
但两个线程一起进来时:
Thread A Thread B
user 不存在?Yes
user 不存在?Yes
load_user()
load_user()
写入 cache
再写一次
如果 load_user() 只是创建一个小对象,问题可能不明显。
可一旦它背后是:
数据库查询
HTTP 请求
昂贵模型计算
文件加载
重复执行就可能非常昂贵,甚至产生副作用。
最直观的修复是:
cache_lock = threading.Lock()
def get_user(user_id):
with cache_lock:
if user_id not in cache:
cache[user_id] = load_user(user_id)
return cache[user_id]
如果这是一个大型服务,还可以进一步考虑:
per-key lock
single-flight
future/promise
concurrent queue
immutable snapshot
目标不是“所有东西都塞进一个大锁”,而是尽可能让同步边界贴合真正的共享资源。
十一、既然 free-threaded,为什么内部还是到处有锁?
这个问题其实很关键。
因为:
free-threaded
并不等于:
lock-free
free-threaded CPython 真正要摆脱的是:
用一个全局 GIL把几乎所有 Python 执行统一串行化。
传统结构可以粗略理解为:
Global GIL
│
┌──────────┼──────────┐
│ │ │
dict list object
而 free-threaded 后,更像是:
dict A lock dict B lock
list A lock list B lock
atomic refcount
critical sections
optimistic reads
其他同步机制
也就是从:
粗粒度全局同步
转向:
细粒度并发控制
PEP 703 还专门针对 dict 和 list 的部分读取设计了乐观访问路径,以减少不必要的锁竞争。(peps.python.org)
所以,“free-threaded”更准确地说是:
线程可以自由并行运行,而不是程序从此不需要任何同步。
十二、为什么一个 C extension 可能让 GIL 又“回来”?
这是 free-threaded Python 从实验走向实际项目时,非常现实的一道关。
假设当前状态是:
你的 Python
↓
free-threaded build
↓
GIL disabled
然后代码执行:
import some_native_library
这个包里包含 C extension。
很多历史悠久的 C extension 在设计时默认了一个前提:
“我运行的时候一定有 GIL。”
于是它内部可能存在:
static PyObject *global_cache;
却没有自己的 mutex。
过去,这种共享状态之所以没立刻暴露问题,很可能就是因为:
GIL
↓
无意中保护了这些全局状态
一旦真正关闭 GIL:
Thread A ───► global_cache
Thread B ───► global_cache
问题就可能升级成真正的 native data race。
此时最坏的结果已经不是“数字少加一次”,而可能是:
use-after-free
内存损坏
segmentation fault
解释器崩溃
因此,free-threaded CPython 要求 C 扩展明确声明自己能够安全地在没有 GIL 的环境下运行。
如果扩展没有声明支持,导入它时 Python 会发出警告,并可能重新启用 GIL。(docs.python.org)
十三、C extension 怎样声明“我不依赖 GIL”?
对于采用 multi-phase initialization 的扩展,可以通过:
Py_mod_gil
来声明。
例如:
static PyModuleDef_Slot module_slots[] = {
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
{0, NULL}
};
关键是:
Py_MOD_GIL_NOT_USED
它表达的意思是:
这个扩展已经处理好了自己的并发安全问题,不要求运行时启用 GIL。
如果没有相应声明,默认会按:
Py_MOD_GIL_USED
处理,也就是认为扩展依赖 GIL。Python 3.14 的 C API 文档已经明确规定这一机制。(docs.python.org)
但如果你自己维护扩展模块,请不要把迁移工作理解成:
加一个 flag
然后结束。
真正需要审计的是:
全局变量
静态缓存
borrowed reference
引用计数
对象生命周期
自定义 allocator
C/C++ shared state
第三方 native library
Python 官方迁移文档也特别提醒:以前由 GIL 隐式保护的 extension global state、cache 等共享资源,现在可能必须自行加锁,或者迁移为 thread-local state。(docs.python.org)
十四、怎样判断当前 Python 是不是 free-threaded build?
这里有一个特别重要的区分。
你其实需要回答两个不同的问题:
当前 Python binary 是否支持 free-threading?
以及:
这个 Python 进程此刻到底有没有开启 GIL?
这两个答案不一定相同。
先检测 build:
import sysconfig
free_threaded_build = (
sysconfig.get_config_var("Py_GIL_DISABLED") == 1
)
print(free_threaded_build)
官方文档推荐通过:
sysconfig.get_config_var("Py_GIL_DISABLED")
判断当前解释器构建是否支持 free threading。(docs.python.org)
你也可以看:
python -VV
free-threaded 构建的版本信息中会包含相应标识。
但这里回答的仍然只是:
“这个解释器有没有无 GIL 能力?”
还没有回答运行时到底开没开。
十五、怎样检测当前进程 GIL 到底有没有启用?
现代 free-threaded CPython 提供了一个非常直接的接口:
sys._is_gil_enabled()
例如:
import sys
print(sys._is_gil_enabled())
返回:
True
表示当前 GIL 正在启用。
返回:
False
则说明当前进程正在无 GIL 模式下运行。Python 官方 free-threading 文档明确推荐使用它判断运行时状态。(docs.python.org)
在实际项目里,我更建议把构建能力和运行状态一起打印出来:
import sys
import sysconfig
def runtime_info():
supports_free_threading = (
sysconfig.get_config_var("Py_GIL_DISABLED") == 1
)
is_gil_enabled = getattr(
sys,
"_is_gil_enabled",
lambda: True,
)()
return {
"python": sys.version,
"free_threaded_build": supports_free_threading,
"gil_enabled": is_gil_enabled,
}
print(runtime_info())
你可能看到:
{
"free_threaded_build": True,
"gil_enabled": False,
}
也完全可能看到:
{
"free_threaded_build": True,
"gil_enabled": True,
}
这并不矛盾。
因为 free-threaded build 仍然支持运行时重新启用 GIL。
十六、free-threaded build 不代表 GIL 永远关闭
你可以通过环境变量:
PYTHON_GIL=1
或者:
python -X gil=1 app.py
显式启用 GIL。
反过来,在支持 free threading 的构建上,也可以请求:
python -X gil=0 app.py
Python 3.14 官方命令行文档提供了 PYTHON_GIL 与 -X gil 的相应配置。(docs.python.org)
这意味着以后排查生产环境里的并发问题,只记录:
sys.version
已经不够细了。
至少建议记录:
Python version
free-threaded build?
GIL currently enabled?
platform
CPU count
关键 native extension 版本
尤其当你发现“同一套代码在开发机和生产机表现完全不同”时,这些信息非常有价值。
十七、项目实战:已有多线程项目怎样迁移到 free-threaded?
如果一个成熟服务准备切到 free-threaded CPython,我不会先换解释器,然后直接跑压测。
更稳妥的第一步,是做一次 shared mutable state audit。
重点搜索:
global
以及:
全局 dict
全局 list
singleton
cache
connection pool
registry
LRU
counter
mutable class attribute
第三方 C extension
例如:
cache = {}
requests_count = 0
plugins = []
然后把共享状态分成几类。
第一类:只读共享状态
例如:
CONFIG = {
"timeout": 10,
"region": "us-west",
}
如果它初始化完成后永远不再修改,风险就低得多。
如果希望把“不允许修改”表达得更明确,还可以:
from types import MappingProxyType
CONFIG = MappingProxyType({
"timeout": 10,
"region": "us-west",
})
这种做法的价值不只是防误改,更是在架构层面告诉维护者:
这是共享数据,但它不是共享可变状态。
第二类:每个线程独立的数据
这类状态完全没必要抢同一份。
可以考虑:
threading.local()
或者更简单,尽量放在函数局部变量中。
只要能让:
Thread A → State A
Thread B → State B
就不要强行设计成:
Thread A ─┐
├─→ Shared State
Thread B ─┘
第三类:真正的共享可变状态
例如:
stats["requests"] += 1
这类代码应该重点审计。
根据业务需要考虑:
Lock
RLock
Condition
Semaphore
Queue
事件循环
消息传递
分片锁
per-key lock
核心原则仍然一样:不要把正确性押在 CPython 当前某个内部实现细节上。
十八、很多时候,用 Queue 比共享容器更自然
假设多个 worker 负责计算结果,一个聚合线程负责统一保存。
一种直觉写法是:
results = []
def worker():
results.append(calculate())
如果任务变复杂,更容易维护的设计往往是明确使用消息传递:
from queue import Queue
import threading
queue = Queue()
def worker():
result = calculate()
queue.put(result)
def collector():
while True:
result = queue.get()
try:
save(result)
finally:
queue.task_done()
架构从:
多个线程共同修改状态
变成:
多个 Producer
↓
Queue
↓
一个 Consumer 管理状态
这种 message passing 思路在 free-threaded 时代会更加重要。
因为真正难维护的从来不是“线程多”,而是:
很多线程都能随时修改同一份复杂状态。
十九、free-threaded 出现后,multiprocessing 是不是就没用了?
当然不是。
free-threaded Python 的确让:
threading
第一次有机会更自然地承担 CPU-bound Python workload。
但工程里选哪一种并发模型,仍然取决于问题本身:
| 场景 | 优先考虑 |
|---|---|
| 大量网络 I/O | asyncio / threading |
| 大量阻塞第三方 API | threading |
| CPU 密集且 free-threaded 兼容 | threading 值得测试 |
| 强隔离需求 | multiprocessing |
| 不可信任务 | process isolation |
| 巨大共享内存数据 | free-threaded threading 很有吸引力 |
| C extension 尚未兼容 | 需要逐库评估 |
free-threaded 很吸引人的地方之一,就是多个线程可以:
共享同一个 Python 对象空间,而不必像 multiprocessing 那样频繁序列化和复制大型数据。
这对于:
AI
科学计算
图计算
数据处理
大型内存缓存
并行搜索
确实有很大的潜力。
但它提供的是新的选择,不是让其他并发模型全部失效。
二十、也别看到 free-threaded 就期待“4 个线程 = 4 倍性能”
取消 GIL 解决的是:
并行执行是否可能
而不是保证:
性能一定线性增长
即使没有 GIL,你的程序仍然可能被这些因素限制:
锁竞争
内存带宽
CPU cache miss
False Sharing
对象分配
引用计数同步
GC
算法串行部分
第三方库内部锁
另外,free-threaded CPython 为并发安全本身也需要付出额外成本。Python 3.14 官方文档给出的 pyperformance 数据显示,相比默认 GIL 构建,free-threaded 模式的单线程 Python 执行仍可能存在额外开销,而且不同硬件与 workload 的差异并不小。(docs.python.org)
所以性能优化仍然绕不开最朴素的流程:
Measure
↓
Change
↓
Measure Again
而不是:
感觉线程应该更快
二十一、一个可以直接跑的 free-threaded benchmark
下面这个 Python 实战代码可以直接放到你的环境中测试:
import sys
import time
import threading
import sysconfig
def calculate(n):
total = 0
for i in range(n):
total += i * i
return total
def worker(n):
calculate(n)
def benchmark(thread_count=4, n=5_000_000):
threads = [
threading.Thread(
target=worker,
args=(n,),
)
for _ in range(thread_count)
]
start = time.perf_counter()
for thread in threads:
thread.start()
for thread in threads:
thread.join()
return time.perf_counter() - start
print(
"free-threaded build:",
sysconfig.get_config_var("Py_GIL_DISABLED") == 1,
)
if hasattr(sys, "_is_gil_enabled"):
print(
"GIL enabled:",
sys._is_gil_enabled(),
)
for count in (1, 2, 4, 8):
elapsed = benchmark(count)
print(
f"{count} threads: "
f"{elapsed:.3f}s"
)
测试时,别只比较:
1 thread
4 threads
还可以继续观察:
throughput
speedup
efficiency
CPU utilization
memory usage
例如:
Speedup = T1 / T4
理想情况下当然希望:
T1 / T4 → 4
但真实程序往往不会线性扩展。
如果最后只能得到 2.2 倍、1.5 倍,甚至线程更多反而更慢,这都不是“测试失败”,而是在告诉你真正的瓶颈在哪里。
二十二、2026 年写 Python 多线程,建议牢牢记住这 7 条
如果要把整篇 Python 教程压缩成一套能直接放进项目里的 Python 最佳实践,可以记住下面七条:
-
不要再把 GIL 当成线程安全机制。
无论传统还是 free-threaded CPython,只要有共享业务状态,就应该明确设计同步策略。 -
一行 Python 代码不等于一个原子事务。
特别警惕+=、check-then-act 和 read-modify-write。 -
内置容器安全,不代表你的业务逻辑安全。
“dict不会被写坏”和“计数一定不会丢”“余额一定不会扣错”是完全不同的保证。 -
优先减少共享可变状态。
local state、immutable data、Queue、message passing 往往比到处加锁更容易维护。 -
认真审计所有 C extension。
NumPy、数据库驱动、AI 库、压缩库以及自研 native module,都可能影响 free-threaded 模式能否真正生效。 -
同时检查 build 和 runtime。
Py_GIL_DISABLED回答“这个解释器支不支持”;sys._is_gil_enabled()回答“这个进程现在到底开没开 GIL”。 -
所有并行性能结论都要 benchmark。
free-threaded 打开的是多核并行的大门,不是一个“自动多倍加速”的开关。
二十三、最后:free-threaded 真正改变的,是我们理解线程安全的方式
过去很多 Python 开发者讨论并发时,首先问的是:
“GIL 会不会挡住我?”
到了 2026 年,更值得问的问题其实变成了:
“如果这些 Python 线程真的同时运行,我的代码还正确吗?”
这才是 free-threaded CPython 最值得关注的变化。
过去一些“碰巧表现得很安全”的代码:
cache[key] = cache.get(key, 0) + 1
一些依赖解释器实现细节的写法:
if key not in cache:
cache[key] = create()
以及过去被 GIL 无形中保护着的 C 扩展:
global state
static cache
borrowed references
现在都值得重新审视。
这其实会让 Python 并发代码变得更健康,因为我们会被迫从:
依赖解释器的隐式保护
走向:
明确描述共享状态
明确设计同步边界
明确表达业务不变量
而这,本来就是高质量并发系统应该具备的设计方式。
所以下一次看到:
counter += 1
别只问:
“这一行是不是原子的?”
可以再往前想一步:
“谁可能同时访问这个状态?同步边界在哪里?这段代码需要维持什么业务不变量?”
当你开始用这种方式审视代码时,你学习的就已经不只是 Python编程、GIL 或 free-threaded Python,而是在真正进入并发系统设计的核心。
参考资料与继续学习
关于 free-threaded CPython 的总体设计,可以继续阅读 PEP 703:Making the Global Interpreter Lock Optional in CPython。(peps.python.org)
关于 Python 3.14 将 free-threading 推进为“正式支持但仍可选”的阶段,可阅读 PEP 779 与 Python 3.14 发布说明。(peps.python.org)
关于 dict、list、set 在 free-threaded Python 中具体有哪些线程安全保证,可以查阅 Python 3.14 的 Thread Safety Guarantees 文档。尤其是做并发开发时,建议关注其中对 atomic、shared-object safe 以及复合操作的区分。(docs.python.org)
如果你维护 C/C++ 扩展,则应该重点阅读 Python 官方的 C API Extension Support for Free Threading,特别是 Py_mod_gil、全局状态、缓存和引用管理相关内容。(docs.python.org)
留给读者的两个问题
如果你的生产服务目前大量依赖:
threading.Thread
你会马上迁移到 free-threaded Python,还是会先逐个验证第三方依赖和 C extension 的兼容性?
另外,一个更值得团队认真讨论的问题是:
当 GIL 不再替我们隐式串行化大量 Python 代码以后,现有项目里究竟还藏着多少从未真正暴露过的 race condition?
欢迎把你遇到的死锁、竞争条件、线程性能优化案例,以及迁移 free-threaded Python 时碰到的问题拿出来讨论。
GIL 的角色正在变化,但并发编程最可靠的原则没有变:共享状态越明确,同步边界越清楚,代码就越值得信赖。

156

被折叠的 条评论
为什么被折叠?



