协程与线程与进程选型决策树

一、协程 vs 线程 vs 进程——选型决策树

1.1 核心区别——一张表

协程(Coroutine)线程(Thread)进程(Process)
调度者程序自己(用户态)操作系统(内核态)操作系统(内核态)
切换开销~纳秒级(函数调用级别)~微秒级(系统调用 + 上下文切换)~毫秒级(地址空间切换)
内存共享——同一个线程共享——同一个进程隔离——各自独立内存空间
GIL 影响不受 GIL 限制受 GIL 限制——同时只能一个线程跑 Python不受 GIL——各自有 Python 解释器
通信方式直接返回值 / 共享变量Queue / Lock / 共享变量(需加锁)Pipe / Queue / 共享内存
创建数量成千上万无压力几十到几百(多了切换开销大)几到几十(启动慢、内存重)
适用场景I/O 密集型(网络、DB)I/O 密集型(需调用不兼容 asyncio 的库)CPU 密集型(计算、图像处理)
崩溃影响一个协程崩溃可能影响整个事件循环一个线程崩溃可能带崩进程一个进程崩溃独立隔离

1.2 为什么 Python 多线程”不行”——GIL 一句话

 
# ❌ 多线程算数学——和单线程一样快(甚至更慢)
 
import threading
 
results = []
 
def compute():
 
    for i in range(10_000_000):
 
        results.append(i * i)       # 这行需要 GIL
 
# ✅ 多线程等 I/O——有用(等 I/O 时会释放 GIL)
 
def download():
 
    resp = requests.get(url)        # 底层 C 代码等网络时会释放 GIL
 
    # 另一个线程可以趁这个空隙执行
 

一句话讲清:“GIL 导致 Python 多线程无法利用多核做计算。但 I/O 密集场景下,线程等在系统调用时会释放 GIL——还是有用的。asyncio 更彻底——单线程跑协程,根本就不碰 GIL。“

1.3 选型决策树


graph TD

    TASK[你要执行什么任务] --> IO[IO密集型<br/>大量网络/数据库/文件操作]

    TASK --> CPU[CPU密集型<br/>大量计算]

    IO --> ASYNC[库支持asyncio → asyncio 首选]

    IO --> THREAD[库不支持asyncio → 多线程 run_in_executor]

    CPU --> PROCESS[多进程 ProcessPoolExecutor 绕开GIL]

 
# 实战:asyncio + 线程池混用——搞定不兼容 asyncio 的库
 
import asyncio
 
import requests   # 同步库,但不支持 asyncio
 
async def download_with_requests(url):
 
    loop = asyncio.get_running_loop()
 
    # 把同步函数丢到线程池执行——不阻塞事件循环
 
    return await loop.run_in_executor(
 
        None,                  # None = 默认线程池
 
        requests.get,          # 同步函数
 
        url                    # 参数
 
    )
 
# 现在可以"像异步一样"并发调用同步 requests 了
 
async def main():
 
    results = await asyncio.gather(
 
        download_with_requests("url1"),
 
        download_with_requests("url2"),
 
        download_with_requests("url3"),
 
    )
 

速记卡(面试闪卡)

Q1:一句话讲清「协程与线程与进程选型决策树」到底是什么?

A:协程、线程、进程是按任务类型选型的三种并发模型:协程管 I/O、进程管 CPU、线程居中。

Q2:核心区别一张表 —— 怎么理解?

A:三者像三种搬砖工:协程(coroutine)是同一个人在任务间秒切(纳秒级、共享内存、不受 GIL);线程(thread)是工厂多个工位的工人(微秒级、受 GIL 同时只一人跑 Python);进程(process)是独立工厂(毫秒级、各占内存、真并行)。

Q3:GIL 一句话 —— 怎么理解?

A:GIL(Global Interpreter Lock)像 Python 工厂的唯一厕所钥匙:同一刻只有一个线程能跑 Python 字节码,多线程算数学和单线程一样快。但等 I/O(网络/磁盘)时会松手,所以 I/O 密集仍有用;asyncio 更狠——单线程跑协程根本不碰这把锁。

Q4:选型决策树 —— 怎么理解?

A:决策像分诊台:I/O 密集→库支持 asyncio 就上协程,不支持就丢线程池 run_in_executor;CPU 密集→多进程 ProcessPoolExecutor 绕开 GIL。一句话:算多用进程,等多用协程/线程。

Q5:asyncio + 线程池混用 —— 怎么理解?

A:遇到不兼容 asyncio 的同步库(如 requests),别在协程里直接调——会堵死事件循环。用 loop.run_in_executor(None, requests.get, url) 把它丢到默认线程池跑,等 I/O 时协程切走,像把脏活外包给临时工不误正事。

Q6:核心速记主线有哪些?

  • 协程:用户态、纳秒切换、不受 GIL、I/O 密集、数量成千上万

  • 线程:内核态、受 GIL(同时一线程跑 Python)、I/O 密集、几十到几百

  • 进程:独立内存、绕开 GIL、CPU 密集、几到几十、崩溃隔离

  • GIL 使多线程不能并行计算,但 I/O 时释放 GIL 仍有用

  • 选型:CPU 密集→多进程;I/O 且支持 asyncio→协程;不支持→线程池

口诀

A:协线程进程三兄弟,调度开销各不同;

GIL 锁住多线程,算数枉费工;

IO 密集用协程,CPU 密集开进程;

不兼容库丢线程池,事件循环莫堵。

相关链接