2026 版 Python GIL 全解:free-threaded CPython 下,线程安全到底该怎么写?

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 自动线程安全?
  • dictlist 内部已经有锁,我们还能不能依赖“原子操作”?
  • 为什么一个 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 给很多内置容器引入了更细粒度的同步方案。

例如 dictlistset 会通过对象级锁、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 还专门针对 dictlist 的部分读取设计了乐观访问路径,以减少不必要的锁竞争。(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/Oasyncio / threading
大量阻塞第三方 APIthreading
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 最佳实践,可以记住下面七条:

  1. 不要再把 GIL 当成线程安全机制。
    无论传统还是 free-threaded CPython,只要有共享业务状态,就应该明确设计同步策略。

  2. 一行 Python 代码不等于一个原子事务。
    特别警惕 +=、check-then-act 和 read-modify-write。

  3. 内置容器安全,不代表你的业务逻辑安全。
    dict 不会被写坏”和“计数一定不会丢”“余额一定不会扣错”是完全不同的保证。

  4. 优先减少共享可变状态。
    local state、immutable data、Queue、message passing 往往比到处加锁更容易维护。

  5. 认真审计所有 C extension。
    NumPy、数据库驱动、AI 库、压缩库以及自研 native module,都可能影响 free-threaded 模式能否真正生效。

  6. 同时检查 build 和 runtime。
    Py_GIL_DISABLED 回答“这个解释器支不支持”;sys._is_gil_enabled() 回答“这个进程现在到底开没开 GIL”。

  7. 所有并行性能结论都要 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)

关于 dictlistset 在 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 的角色正在变化,但并发编程最可靠的原则没有变:共享状态越明确,同步边界越清楚,代码就越值得信赖。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

铭渊老黄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值