我用 Python 多线程并行跑 CPU 密集计算想提速,结果开了 8 个线程比单线程还慢,我对着 GIL 排查了大半天的复盘

写了个 CPU 密集型计算任务,想当然用多线程并行加速,开 8 个线程以为快 8 倍。结果目瞪口呆:开 8 个线程不但没快反而比单线程还慢一点;CPU 监控更困惑——8 核机器,8 个线程加起来只用满约 1 个核,其他 7 个核基本闲着。排查大半天才理解 Python 又爱又恨绕不开的设计——GIL(全局解释器锁):它规定同一时刻只有一个线程能执行 Python 字节码,所以即使开 8 线程、有 8 核,这些线程也不能真正同时执行 Python 代码、只能轮流抢 GIL,CPU 密集任务多线程≈串行还多了 GIL 切换开销所以不快反慢。关键是 GIL 对 CPU 密集和 IO 密集影响完全不同:CPU 密集线程占着 GIL 无法并行(多线程无效),IO 密集等 IO 时会释放 GIL(多线程有效)。这篇从 GIL 详解、CPU 密集用多进程(各自 GIL 真并行)/IO 密集用多线程或 asyncio 的正解、CPU vs IO 密集怎么判断、并发模型选择速查、GIL 常见误解、决策图与铁律,到附上一段亲手对比单线程/多线程/多进程耗时的 benchmark。核心领悟:跨技术学习最危险的不是不懂的新东西而是以为我懂、其实在这里不成立的旧经验(别的语言多线程能并行加速、Python 因 GIL 不成立);精确理解概念(并发vs并行)才能正确选型;性能是反直觉重灾区,别想当然要动手 benchmark 用数据说话。

我用 Python 多线程并行跑 CPU 密集计算想提速,结果开了 8 个线程比单线程还慢,我对着 GIL 排查了大半天的复盘

那是我写的一个 CPU 密集型的计算任务:一大批数据,每条都要做一堆纯计算(数学运算、循环处理)。我想当然地用了多线程来"并行加速":开 8 个线程,以为能快 8 倍。结果跑出来的数据让我目瞪口呆:开了 8 个线程,不但没快 8 倍,反而比单线程还慢了一点。我盯着 CPU 监控更困惑了:我机器明明是 8 核,可跑的时候,8 个线程加起来只用满了大约 1 个核的算力,其他 7 个核基本闲着!多线程不是为了利用多核并行吗?怎么 8 个线程挤在 1 个核上跑、还互相拖累?排查了大半天,我才真正理解了 Python 那个又爱又恨、绕不开的设计:GIL(全局解释器锁)。这篇就把这场"多线程不快反慢"的事故,从头复盘一遍。

故障现场:8 线程比单线程还慢,8 核只用满 1 核

先看现场。多线程在 CPU 密集任务上,完全没起到并行加速的效果:

import threading, time

# CPU 密集型任务: 纯计算(没有 IO)
def cpu_heavy(n):
    total = 0
    for i in range(n):
        total += i * i   # 纯 CPU 计算
    return total

N = 50_000_000

# 单线程: 串行跑 8 次
start = time.time()
for _ in range(8):
    cpu_heavy(N)
print(f"单线程: {time.time() - start:.2f}s")   # 比如 ~8s

# 多线程: 8个线程"并行"跑 (我以为会快很多)
start = time.time()
threads = [threading.Thread(target=cpu_heavy, args=(N,)) for _ in range(8)]
for t in threads: t.start()
for t in threads: t.join()
print(f"8线程:  {time.time() - start:.2f}s")    # ✗ 比如 ~8.5s, 比单线程还慢!

# 现象: 8核机器, 但 8 个线程加起来只用满约 1 个核(其他核闲着)!

# 为什么? 因为 Python 的 GIL(全局解释器锁):
# 1. GIL = Global Interpreter Lock, 是 CPython 解释器的一把"全局锁"。
# 2. 它规定: 【同一时刻, 只有一个线程能执行 Python 字节码】。
#    → 即使你开了 8 个线程、机器有 8 个核, 这 8 个线程也【不能真正同时】
#      执行 Python 代码! 它们要【轮流】抢 GIL, 一次只有一个在跑。
# 3. 所以对 CPU 密集任务: 8个线程 ≈ 还是串行(轮流执行), 没有并行加速!
#    → 8个线程的计算量, 还是挤在"1个核的算力"上轮流做。
# 4. 而且: 线程还要不断"抢GIL、释放GIL、切换", 这个开销让它比单线程还慢!

# 现象拼图:
#   - GIL 让"同一时刻只有一个线程执行Python字节码" → 多线程无法并行算CPU。
#   - CPU密集任务: 多线程 = 串行 + 额外的GIL切换开销 → 不快反慢。
#   - 8核只用满1核, 正是因为同时只有1个线程在执行Python代码。
#   - ★ 根因: 我用了"多线程"去加速"CPU密集"任务, 但 GIL 决定了
#     Python多线程对CPU密集任务无效。我用错了并发模型。

看清真相后,我才明白这"不快反慢"的根子。问题的根源,是 Python 的 GIL(全局解释器锁)。GIL 是 CPython 解释器的一把"全局锁",它规定:同一时刻只有一个线程能执行 Python 字节码所以,即使我开了 8 个线程、机器有 8 个核,这 8 个线程也不能真正同时执行 Python 代码——它们要轮流抢 GIL,一次只有一个在跑对 CPU 密集任务而言:8 个线程 ≈ 还是串行(轮流执行),没有并行加速,8 个线程的计算量还是挤在"1 个核的算力"上轮流做(所以 8 核只用满 1 核);而且线程还要不断抢 GIL、释放、切换,这个额外开销让它比单线程还慢根因是:我用了"多线程"去加速"CPU 密集"任务,但 GIL 决定了 Python 多线程对 CPU 密集任务无效——我用错了并发模型

第一件事:搞懂 GIL 是什么、它影响什么

要解决它,得先彻底搞懂 GIL,以及它对不同类型任务的不同影响。

GIL(全局解释器锁)详解

# 一、GIL 是什么?
#   - CPython(最主流的Python解释器)里的一把"全局互斥锁"。
#   - 规定: 同一时刻, 只有一个线程能持有GIL、执行Python字节码。
#   - 即: Python的多线程, 无法在多核上"真正并行地执行Python代码"。

# 二、为什么有GIL?(历史/设计原因)
#   - CPython的内存管理(引用计数)不是线程安全的。
#   - GIL用"一把大锁"简单粗暴地保证了线程安全(同时只有一个线程动内存)。
#   - 代价: 牺牲了多线程的并行计算能力, 换取了实现的简单和单线程的高效。

# 三、关键: GIL对"CPU密集"和"IO密集"任务的影响【完全不同】!
#   - CPU密集任务(纯计算: 数学/循环/图像处理):
#     * 线程大部分时间在"执行Python字节码"、占着GIL。
#     * 多线程也只能轮流执行 → 无法并行 → 多线程【无效甚至更慢】。
#   - IO密集任务(网络请求/文件读写/数据库查询):
#     * 线程在等IO时(如等网络响应), 会【释放GIL】!
#     * 这时其他线程可以拿到GIL去执行 → 实现了"并发"(不是并行, 但有效)。
#     * 所以多线程对IO密集任务【是有效的】(等IO时切换去干别的)。

# 四、所以并发模型的选择, 取决于任务类型:
#   - CPU密集 → 用【多进程】(每个进程有自己的GIL和解释器, 真正并行多核)。
#   - IO密集  → 用【多线程】或【异步asyncio】(等IO时切换, GIL不是瓶颈)。
#   - 混合     → 按主要瓶颈选, 或组合使用。

# 五、注意: GIL 是 CPython 的特性, 不是Python语言规范
#   - 其他实现(Jython/IronPython)没有GIL; PyPy有。
#   - Python 3.13 开始有"无GIL"的实验性版本(free-threaded), 未来可能改变。

# 核心: GIL是CPython的全局锁, 同时只一个线程执行Python字节码、多线程无法并行算CPU;
#   CPU密集任务多线程无效(用多进程), IO密集任务等IO释放GIL故多线程有效(或用asyncio)。

想透 GIL,这个诡异的现象就清楚了。一、GIL 是什么?——CPython 里的一把全局互斥锁,规定同一时刻只有一个线程能持有它、执行 Python 字节码,即 Python 多线程无法在多核上真正并行执行 Python 代码二、为什么有 GIL?——CPython 的内存管理(引用计数)不是线程安全的,GIL 用一把大锁简单保证了线程安全,代价是牺牲了多线程的并行计算能力三、关键:GIL 对"CPU 密集"和"IO 密集"任务的影响完全不同!——CPU 密集任务(纯计算)线程大部分时间占着 GIL、多线程只能轮流执行、无法并行(无效甚至更慢);IO 密集任务(网络/文件/数据库)线程在等 IO 时会释放 GIL、其他线程可以拿到 GIL 执行、实现了有效的"并发",所以多线程对 IO 密集任务是有效的四、并发模型的选择取决于任务类型:CPU 密集用多进程(每个进程有自己的 GIL,真正并行多核)、IO 密集用多线程或异步 asyncio五、注意:GIL 是 CPython 的特性,不是 Python 语言规范(Python 3.13 起有无 GIL 的实验版本)。

第二件事:正解——CPU密集用多进程,IO密集用多线程/异步

搞懂了原理,正解就清晰了:CPU 密集任务用多进程(绕过 GIL 真正并行)、IO 密集任务用多线程或异步、按任务类型选对并发模型

# ====== 正解一(CPU密集): 用多进程 multiprocessing 绕过 GIL ======
from multiprocessing import Pool
import time

def cpu_heavy(n):
    total = 0
    for i in range(n):
        total += i * i
    return total

N = 50_000_000
if __name__ == "__main__":   # ★ 多进程必须放在 __main__ 保护下
    start = time.time()
    with Pool(8) as pool:    # 8个进程, 每个进程独立的解释器和GIL
        pool.map(cpu_heavy, [N] * 8)
    print(f"8进程: {time.time() - start:.2f}s")   # ✓ 接近单线程的1/8! 真正并行多核
# → 每个进程有自己的GIL, 8个进程真正在8个核上并行 → CPU密集任务大幅加速!
#   代价: 进程比线程重(创建慢、内存独立、进程间通信有开销)。

# ====== 正解二(IO密集): 用多线程(等IO时释放GIL)======
import threading, requests

def fetch(url):
    return requests.get(url).text   # 网络IO, 等响应时会释放GIL

urls = [...] * 100
threads = [threading.Thread(target=fetch, args=(u,)) for u in urls]
for t in threads: t.start()
for t in threads: t.join()
# → IO密集任务: 一个线程等网络时释放GIL, 其他线程趁机执行 → 多线程有效!

# ====== 正解三(IO密集, 更推荐): 用异步 asyncio ======
import asyncio, aiohttp

async def fetch_async(session, url):
    async with session.get(url) as resp:
        return await resp.text()

async def main(urls):
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_async(session, u) for u in urls]
        return await asyncio.gather(*tasks)   # 并发发起所有请求
# asyncio.run(main(urls))
# → 单线程内用事件循环并发处理大量IO, 比多线程更轻量、更高效(高并发IO首选)。

# ====== 正解四: 用 concurrent.futures 统一接口(方便切换)======
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
# CPU密集 → ProcessPoolExecutor; IO密集 → ThreadPoolExecutor
with ProcessPoolExecutor(max_workers=8) as ex:   # CPU密集
    results = list(ex.map(cpu_heavy, [N] * 8))
with ThreadPoolExecutor(max_workers=20) as ex:   # IO密集
    results = list(ex.map(fetch, urls))
# → 两者接口几乎一样, 按任务类型换一个类名即可, 很方便。

# ====== 正解五: CPU密集还可考虑 ======
# - 用 NumPy/Pandas 等底层用C实现的库(它们在C层面计算, 会释放GIL)。
# - 用 Cython / Numba 把热点代码编译成机器码。
# - 把计算下沉到 C 扩展(C扩展可以释放GIL)。

# 核心: 按任务类型选并发模型 —— CPU密集用多进程(每进程独立GIL真正并行多核)、
#   IO密集用多线程(等IO释放GIL)或asyncio(更轻量); 用concurrent.futures统一接口方便切换。

修复的核心,是"按任务类型选对并发模型——CPU 密集用多进程,IO 密集用多线程/异步"正解一(CPU 密集):用多进程 multiprocessing 绕过 GIL——每个进程有自己独立的解释器和 GIL,8 个进程真正在 8 个核上并行,CPU 密集任务大幅加速(接近 1/8 时间);代价是进程比线程重(创建慢、内存独立、进程间通信有开销);注意多进程要放在 if __name__ == "__main__" 保护下正解二(IO 密集):用多线程——线程等 IO 时释放 GIL、其他线程趁机执行,多线程对 IO 密集有效正解三(IO 密集,更推荐):用异步 asyncio——单线程内用事件循环并发处理大量 IO,比多线程更轻量高效,高并发 IO 首选正解四:用 concurrent.futures 统一接口——CPU 密集用 ProcessPoolExecutor、IO 密集用 ThreadPoolExecutor,接口几乎一样,换个类名即可正解五:CPU 密集还可用 NumPy/Pandas(C 层计算会释放 GIL)、Cython/Numba 编译、C 扩展归根结底:CPU 密集用多进程(每进程独立 GIL 真正并行)、IO 密集用多线程或 asyncio;用 concurrent.futures 统一接口方便切换。

第三件事:CPU 密集 vs IO 密集,怎么判断

排查后我意识到,选对并发模型的前提是"先判断任务是 CPU 密集还是 IO 密集"。我把判断方法梳理了一遍。

CPU密集 vs IO密集: 怎么判断, 怎么选

# 一、什么是 CPU 密集任务?
#   - 大部分时间在"计算"(占用CPU): 数学运算、循环、加解密、压缩、
#     图像/视频处理、机器学习推理(纯计算部分)。
#   - 特征: CPU使用率高, 没怎么等外部资源。
#   - → 用【多进程】(绕过GIL, 真正并行多核)。

# 二、什么是 IO 密集任务?
#   - 大部分时间在"等待"(IO等待, CPU闲着): 网络请求、文件读写、
#     数据库查询、调用外部API、等用户输入。
#   - 特征: CPU使用率低, 大量时间在等外部资源响应。
#   - → 用【多线程】或【异步asyncio】(等IO时切换去干别的)。

# 三、怎么判断一个任务是哪种?
#   - 看它"主要时间花在哪": 花在算(CPU密集) 还是 等(IO密集)。
#   - 跑一下看CPU使用率: 高(满核)→CPU密集; 低(核闲着)→IO密集。
#   - 看代码: 全是计算/循环 → CPU密集; 有大量 requests/open/db查询 → IO密集。

# 四、选错的后果(本文):
#   - CPU密集用多线程: GIL导致无法并行 → 不快反慢(本文的坑!)。
#   - IO密集用多进程: 能用但太重(进程开销大), 不如多线程/异步轻量高效。
#   - → 选对模型, 性能可能差几倍到几十倍。

# 五、混合任务:
#   - 有的任务既有计算又有IO → 按"主要瓶颈"选, 或拆分处理。
#   - 如: 用多进程处理CPU部分, 每个进程内用异步处理IO部分。

# 核心: CPU密集(时间花在算/CPU满)用多进程绕GIL并行; IO密集(时间花在等/CPU闲)用多线程或asyncio;
#   先判断任务类型(看时间花在算还是等、看CPU使用率)再选模型, 选错性能差几倍到几十倍。

排查让我明白,选对并发模型的前提是"先判断任务类型"。一、CPU 密集任务:大部分时间在计算(数学运算、循环、加解密、图像处理、ML 推理),CPU 使用率高、没怎么等外部资源 → 用多进程(绕过 GIL 真正并行)二、IO 密集任务:大部分时间在等待(网络请求、文件读写、数据库查询、调外部 API),CPU 使用率低、大量时间等响应 → 用多线程或异步 asyncio三、怎么判断?——看主要时间花在哪(算 vs 等)、跑一下看 CPU 使用率(满核=CPU 密集、核闲=IO 密集)、看代码(全是计算=CPU 密集、有大量 requests/open/db=IO 密集)四、选错的后果(本文):CPU 密集用多线程被 GIL 拖累不快反慢、IO 密集用多进程太重;选对模型性能可能差几倍到几十倍五、混合任务:按主要瓶颈选或拆分(多进程处理 CPU 部分、进程内异步处理 IO 部分)下面这张图,是这次多线程不快反慢的成因与解法:

第四件事:并发模型选择速查

这次踩坑后,我把 Python 三种并发方式和任务类型的匹配整理成一张表。

并发方式 适合任务 能并行多核吗 开销
多线程 threading IO 密集 ✗ 不能(GIL) 较轻
多进程 multiprocessing CPU 密集 ✓ 能(各自GIL) 重(进程独立)
异步 asyncio 高并发 IO ✗ 单线程 最轻
多线程跑CPU密集(本文) ✗ 错误用法 ✗ 还更慢
多进程跑IO密集 △ 能用但浪费 太重

这张表,把"什么任务用什么并发"讲清了:IO 密集用多线程/异步、CPU 密集用多进程它给我的最大启发是:"并发"不是一个笼统的概念,而是有多种模型,每种模型都有它"擅长的场景"和"致命的盲区";用对了事半功倍,用错了(像本文)反而比不用并发还慢而选对的关键,在于理解一个核心区分:"并行(parallel)"和"并发(concurrent)"是两回事——"并行"是"真的同时在多个核上执行"(多进程能做到),"并发"是"交替执行、宏观上同时推进"(多线程/异步在等待时切换、宏观上同时处理多个任务)CPU 密集任务需要的是"并行"(真的同时算),所以要多进程;IO 密集任务需要的是"并发"(等的时候去干别的),所以多线程/异步就够。这让我领悟到:很多概念(并发/并行、同步/异步、进程/线程),只有真正理解了它们"精确的区别和各自解决的问题",才能在面对一个具体任务时,做出正确的技术选型;含混地理解(以为"多线程就是并行加速"),就会像我一样选错、踩坑精确理解概念,是正确选型的前提。

第五件事:关于 GIL 的常见误解

这次也让我澄清了几个关于 GIL 的常见误解。我把它们梳理了一下。

误解 真相
Python 多线程没用 对IO密集有用(等IO释放GIL),只是对CPU密集没用
有GIL所以Python不能并发 能并发(多线程/异步),只是CPU密集不能并行
GIL是Python语言的缺陷 是CPython实现的选择,其他实现可无GIL
多进程总比多线程好 看任务,IO密集多线程更轻量
开越多线程越快 CPU密集多开线程反而更慢(切换开销)
NumPy计算也受GIL限 NumPy在C层计算会释放GIL,能利用多核

这张表,澄清了几个关于 GIL 的常见误解。最关键的两个:"Python 多线程没用"是错的——它对 IO 密集任务很有用(等 IO 时释放 GIL),只是对 CPU 密集没用;"有 GIL 所以不能并发"也是错的——能并发(多线程/异步交替推进),只是 CPU 密集不能并行(同时算)它给我的最大启发是:对一个复杂的、有适用边界的技术特性(如 GIL),"简单化、一刀切的结论"(如"多线程没用")往往是错的、有害的;真相通常是"它在某些情况下有用、某些情况下没用",需要你理解清楚它的边界,而不是记一个粗暴的结论我之前对 GIL 的理解,就停留在"听说 Python 有 GIL、多线程不行"这种道听途说的、模糊的结论上,既没搞清它具体限制什么(CPU 密集的并行),也没搞清它不限制什么(IO 密集的并发),结果在该用多线程的地方可能不敢用、在不该用的地方(CPU 密集)用了。这让我领悟到一个学习技术的态度:对任何技术,都要追求"准确而有边界的理解",而非满足于"模糊而绝对的结论";"X 不行/X 很好"这种一刀切的判断,几乎总是不准确的——真相往往是"X 在什么条件下行、在什么条件下不行"知其然、更知其所以然和其边界——这才是真正掌握一个技术。

第六件事:要做并发提速时,我现在的决策习惯

现在每当我想"用并发提速",我都会按这张图先判断任务类型、再选模型:

这张图的精髓,是"提速前,先判断任务类型,再选对并发模型"第一步永远是 "判断任务是 CPU 密集还是 IO 密集"(看时间花在算还是等、看 CPU 使用率)。然后:CPU 密集用多进程(ProcessPool)、IO 密集用 asyncio(大量并发)或多线程(中等);CPU 密集还可用 NumPy/Cython 在 C 层释放 GIL 加速最后一步是我现在的硬习惯:跑一下对比单线程,确认真的快了(这次的坑正是因为想当然以为多线程快、却没实测对比,结果反而更慢)。这套习惯,让我做并发时,从"想加速就上多线程"变成了"先判断任务类型、选对模型、并实测验证"——核心始终是:Python 有 GIL,CPU 密集要用多进程、IO 密集才用多线程/异步,选错不快反慢,要实测验证。

我立下的几条规矩

这场"多线程不快反慢"的事故,换来了我写 Python 并发时,刻进骨子里的几条铁律:

  1. Python 有 GIL,同时只一个线程执行字节码。多线程无法并行算 CPU。
  2. CPU 密集任务用多进程。每个进程独立 GIL,真正并行多核。
  3. IO 密集任务用多线程或异步。等 IO 时释放 GIL,多线程/asyncio 有效。
  4. 用并发前先判断任务类型。看时间花在"算"还是"等",看 CPU 使用率。
  5. CPU 密集别用多线程。不仅不并行,GIL 切换开销还让它比单线程慢。
  6. 用 concurrent.futures 统一接口。ProcessPool/ThreadPool 方便按任务切换。
  7. 并发提速要实测验证。别想当然,跑一下对比单线程,确认真的快了。

附:一段亲手对比单线程/多线程/多进程的实验

口说无凭。下面这段代码,把同一个 CPU 密集任务用三种方式跑、对比耗时,让 GIL 的影响一目了然:

import time
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor

def cpu_heavy(n):
    total = 0
    for i in range(n):
        total += i * i
    return total

N = 30_000_000
TASKS = 8

def benchmark():
    # ====== 1. 单线程: 串行跑8次 ======
    start = time.time()
    for _ in range(TASKS):
        cpu_heavy(N)
    t_single = time.time() - start
    print(f"单线程串行:   {t_single:.2f}s")

    # ====== 2. 多线程: 8个线程(受GIL限制)======
    start = time.time()
    with ThreadPoolExecutor(max_workers=TASKS) as ex:
        list(ex.map(cpu_heavy, [N] * TASKS))
    t_thread = time.time() - start
    print(f"8线程:        {t_thread:.2f}s  (相对单线程: {t_single/t_thread:.2f}x)")

    # ====== 3. 多进程: 8个进程(绕过GIL, 真并行)======
    start = time.time()
    with ProcessPoolExecutor(max_workers=TASKS) as ex:
        list(ex.map(cpu_heavy, [N] * TASKS))
    t_process = time.time() - start
    print(f"8进程:        {t_process:.2f}s  (相对单线程: {t_single/t_process:.2f}x)")

if __name__ == "__main__":   # ★ 多进程必须在 __main__ 保护下
    benchmark()

"""
典型输出(8核机器, CPU密集任务):
   单线程串行:   8.20s
   8线程:        8.50s  (相对单线程: 0.96x)   ← 比单线程还慢! GIL限制
   8进程:        1.30s  (相对单线程: 6.31x)   ← 快了6倍多! 真正并行多核

结论一目了然:
- CPU密集任务, 多线程(0.96x)不但没加速反而更慢(GIL+切换开销)。
- 多进程(6.31x)真正利用了多核, 大幅加速。
- (如果把 cpu_heavy 换成 IO 密集任务如 time.sleep/网络请求,
   多线程就会有明显加速 —— 因为等待时释放了GIL)
"""

# 核心: 同一CPU密集任务, 单线程8.2s、8线程8.5s(反而更慢)、8进程1.3s(快6倍);
#   跑一遍, GIL让多线程对CPU密集无效、多进程才真正并行 —— 这个对比比任何文字都直观。

这段对比代码,把"GIL 对 CPU 密集任务的影响"变成了三行刺眼的耗时数字。它用同一个 CPU 密集任务,分别跑单线程、多线程、多进程,输出清清楚楚地显示:单线程 8.2s、8 线程 8.5s(相对单线程 0.96x,不但没加速反而更慢!)、8 进程 1.3s(6.31x,快了 6 倍多)这一组数字,比任何文字解释都更有力地证明了:CPU 密集任务下,多线程被 GIL 拖累(无效甚至更慢),而多进程才能真正利用多核并行加速而代码注释里那句提醒——如果换成 IO 密集任务,多线程就会有明显加速(因为等待时释放了 GIL)——则点出了"选对模型取决于任务类型"的关键。这,正是我想用这段代码,留给每个 Python 开发者的最后一课:对于"性能"这种"容易凭直觉想当然、但直觉常常错"的东西,最可靠的判断方式永远是"跑个 benchmark、用真实的数字说话"我这次的坑,根子就在于"我'以为'多线程会快,却从没'测过'";而一个简单的对比 benchmark,就能在几秒钟内,把"以为"和"事实"之间的鸿沟暴露无遗这也呼应了我在整个系列复盘里反复强调的态度:无论是性能、还是行为、还是各种"我以为是这样"的判断,都别停留在猜测,动手写个最小的实验/benchmark,让数据和现象告诉你真相尤其是性能优化——它是"反直觉的重灾区",凭感觉优化常常南辕北辙,唯有测量,才能让你优化在真正的瓶颈上、并确认优化真的有效。不测量,不优化;先 benchmark,再下结论。

写在最后

回头看,这场由 GIL 引发的、多线程不快反慢的事故,真正教给我的,远不止"CPU 密集用多进程"这一个知识点。它让我对"想当然地套用经验"的危险,有了又一次深刻的体会。我栽跟头,是因为我把一个在别的语言里(如 Java、C++)成立的"常识"——"多线程能利用多核并行加速 CPU 计算"——想当然地、不加验证地套用到了 Python 上。可 Python 因为 GIL 的存在,这个"常识"偏偏不成立我用一个"在我熟悉的世界里正确"的认知,去套一个"规则不同的新世界",于是栽了。这让我领悟到一个跨语言、跨技术学习时极其重要的道理:每一门语言、每一个技术,都有它独特的设计、约束和"潜规则";那些在 A 技术里天经地义的"常识",到了 B 技术里,可能因为 B 的某个独特设计(如 Python 的 GIL)而完全失效、甚至相反所以,当我们带着过往的经验进入一个新领域时,最危险的不是"我不懂的新东西"(那我会去学),而是"我以为我懂、其实在这里不成立的旧经验";这些"过时的常识",会让我们跳过验证、直接套用,从而在新规则下碰壁这件事给我的最大警示是:对一门新语言/新技术,不要急于套用旧经验,而要先搞清楚"它有哪些独特的设计和约束、哪些我习以为常的常识在这里不成立"(对 Python 而言,GIL 就是这样一个必须知道的"独特约束");并且,对任何"想当然的性能优化",都要动手实测验证,而不是凭旧经验拍脑袋不让过时的常识误导自己、用实测代替想当然——这,是我用一次"多线程反而更慢"的事故,换来的、关于 Python、也关于"跨技术经验迁移之陷阱"的、最朴素也最深刻的领悟。如果这篇复盘,能让你下次想用 Python 多线程加速计算前,先想想 GIL、先判断任务类型,那我对着那个比单线程还慢的 8 线程熬的这大半天,就值了。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

我给 Agent 配了删数据、发邮件、调付费接口的工具,没设任何护栏,结果它一顿"自主操作"删错了数据、群发了邮件,我对着 Agent 的权限与确认机制排查了大半天的复盘

2026-6-2 8:22:10

技术教程

我复制了一个对象去改副本,结果原对象也跟着被改了、数据莫名其妙被污染,我对着 JavaScript 的浅拷贝和引用共享排查了大半天的复盘

2026-6-2 8:34:07

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索