3、Python并发基石:GIL锁的真相、多线程 vs 多进程的选择、协程的引入

聊到Python并发,有个绕不开的话题——GIL。很多新手一上来就问我:“Python多线程是不是假的?” 嗯,这个问题背后,其实藏着对GIL深深的误解。今天我就带大家把这层窗户纸捅破。

3.1 GIL锁:它到底是什么?

GIL,全称Global Interpreter Lock,全局解释器锁。说白了,就是CPython解释器里的一把大锁。它规定:同一时刻,只有一个线程能执行Python字节码

为什么会这样?这得从Python的内存管理说起。Python的对象引用计数是线程不安全的。如果没有GIL,两个线程同时修改一个对象的引用计数,内存就乱了。我早期做C扩展时,就踩过这个坑——手动管理引用计数,稍不留神就段错误。

核心结论:GIL保护的是Python解释器内部状态,不是你的业务逻辑。它让单线程代码跑得更快,但限制了多线程的并行能力。

3.2 GIL的真相:它真的那么糟糕吗?

其实,GIL对I/O密集型任务影响不大。为什么?因为I/O操作会释放GIL。比如网络请求、文件读写,线程在等待I/O时,GIL会被释放,其他线程就能趁机执行。

我做过一个爬虫项目,用多线程抓取1000个网页。单线程要跑2分钟,多线程(8个线程)只要30秒。你看,GIL并没有成为瓶颈。

但CPU密集型任务就惨了。比如图像处理、数值计算,每个线程都需要大量CPU时间。GIL会让它们轮流执行,实际上变成了单核效果。我曾经用多线程做矩阵乘法,结果比单线程还慢——因为线程切换有开销。

任务类型 多线程效果 原因
I/O密集型 显著提升 I/O等待时释放GIL
CPU密集型 无提升甚至下降 GIL限制并行执行
混合型 部分提升 取决于I/O占比

避坑指南:我曾经在数据处理任务中用了多线程,结果发现CPU利用率只有100%(单核)。后来换成多进程,CPU直接飙到800%。记住:CPU密集型用多进程,I/O密集型用多线程

3.3 多线程 vs 多进程:怎么选?

这个问题,我每次面试都会问。很多人背过答案,但真正遇到场景就懵了。

多线程(threading)

  • 共享内存,数据传递方便
  • 创建开销小,适合大量并发连接
  • 受GIL限制,不适合CPU密集型
  • 典型场景:Web服务器、爬虫、GUI应用

多进程(multiprocessing)

  • 每个进程有独立GIL,真正并行
  • 内存隔离,数据传递需要序列化
  • 创建开销大,进程数不宜过多
  • 典型场景:图像处理、科学计算、机器学习

我个人的习惯是:先问自己“瓶颈在CPU还是I/O?” 如果答案是I/O,用多线程+协程;如果是CPU,用多进程。如果两者都有,那就混合使用——多进程处理计算,每个进程内用多线程处理I/O。

注意:多进程不是越多越好。进程数超过CPU核心数,反而会因为上下文切换降低性能。一般设置为CPU核心数或核心数+1。

3.4 协程的引入:轻量级并发方案

协程,说白了就是用户态线程。它不依赖操作系统调度,而是由程序自己控制。Python的协程通过async/await实现,底层是事件循环。

为什么需要协程?你想想看,多线程有GIL限制,多进程又太重。对于I/O密集型任务,协程是最优雅的方案——单线程内就能实现高并发。

我做过一个WebSocket服务,需要同时处理10万+连接。如果用多线程,每个线程至少8KB栈空间,10万线程就是800MB内存,还不算切换开销。换成协程,每个协程只占几百字节,10万协程轻松搞定。

import asyncio

async def fetch_data(url):
    # 模拟网络请求
    await asyncio.sleep(1)
    return f"Data from {url}"

async def main():
    tasks = [fetch_data(f"http://example.com/{i}") for i in range(100)]
    results = await asyncio.gather(*tasks)
    print(f"Fetched {len(results)} items")

asyncio.run(main())

这段代码启动了100个协程,每个协程模拟1秒的网络请求。总耗时只有1秒左右——因为它们在等待时都让出了控制权。

3.5 三者的选择策略

嗯,这里我总结一下选择策略:

  1. I/O密集型 + 高并发连接:首选协程(asyncio)。如果协程生态不完善,退而求其次用多线程。
  2. CPU密集型 + 计算量大:多进程(multiprocessing)。注意进程间通信开销。
  3. 混合型任务:多进程 + 协程。进程处理计算,进程内协程处理I/O。
  4. 简单脚本/小工具:单线程就够了,别为了并发而并发。

我的经验:不要一开始就上并发。先写单线程版本,用性能分析工具找出瓶颈。很多时候,优化算法比引入并发更有效。我曾经优化过一个排序任务,把冒泡排序换成快速排序,性能提升了100倍——比任何并发方案都管用。

3.6 知识体系总览

下面这张图,是我梳理的Python并发知识体系。你可以把它当作一张地图,随时回来查阅。

Python并发基石知识体系 Python并发 多线程 多进程 协程 特点 共享内存,轻量级 受GIL限制 适合I/O密集型 特点 独立GIL,真正并行 内存隔离 适合CPU密集型 特点 用户态调度 单线程高并发 适合I/O密集型 选择策略总结 I/O密集型 → 协程 > 多线程 CPU密集型 → 多进程 混合型 → 多进程 + 协程

这张图把今天讲的内容串起来了。你可以看到,GIL是理解多线程的关键,而协程则提供了另一种思路。三者各有适用场景,没有银弹。

最后说一句:并发不是目的,提升性能才是。别为了用并发而用并发,先想清楚你的瓶颈在哪。


公众号:蓝海资料掘金营,微信deep3321