threading、multiprocessing、asyncio 到底怎么选?一张图 + 三组实测数据说清楚

「Python 进阶之路」系列 Day21

写在前面

Day16 到 Day20 分别把 GIL、多线程、多进程、asyncio 协程逐个讲透了,模块四的收官篇把这三种并发方案放到同一套 benchmark 下直接对比------不是空谈"该用哪个",而是用实测数据和一张决策图,把选择标准钉死。


一、是什么:三种方案的本质回顾

  • threading:真正的操作系统线程,共享同一进程的内存空间,但受 CPython 的 GIL 限制,同一时刻只有一个线程能执行 Python 字节码(Day16、Day17)
  • multiprocessing:真正的操作系统进程,每个进程有独立的内存空间和独立的 GIL,能真正利用多核 CPU 并行计算,代价是进程间不共享内存、创建开销更大(Day18)
  • asyncio :单线程内的协作式调度,协程在 await 点主动让出控制权,靠 event loop 在等待间隙切换任务,本质是并发不是并行(Day19、Day20)

二、为什么要对比:任务类型决定最优选择

选哪个方案,核心看两个维度:任务是 CPU 密集型还是 I/O 密集型 ,以及并发数量级有多大。前四篇分别验证过三者各自的表现,这一篇把它们放在同一套 benchmark 下直接对比,用数据说话。


三、怎么用:三组实测对比

1. CPU 密集型任务对比

用一个判断大数是否为质数的纯计算任务,4 份同样的工作分别用单线程顺序、ThreadPoolExecutorProcessPoolExecutor 执行:

python 复制代码
def is_prime(n):
    if n < 2:
        return False
    for i in range(2, int(n ** 0.5) + 1):
        if n % i == 0:
            return False
    return True

def cpu_task():
    nums = [100_000_000_000_037 + i * 2 for i in range(3)]
    return [is_prime(n) for n in nums]

实测结果:

方式 耗时 相对单线程比值
单线程顺序执行 0.19s 1.00
多线程(ThreadPoolExecutor) 0.18s 0.97(几乎没有加速)
多进程(ProcessPoolExecutor) 0.11s 0.57(明显加速)

和 Day16-18 的结论完全一致:CPU 密集型任务,多线程因为 GIL 限制基本没有收益,多进程能拿到真实的并行加速。

2. I/O 密集型任务对比

50 个各耗时 0.1 秒的"I/O 任务",分别用多线程和 asyncio 并发执行:

python 复制代码
def io_task_sync(seconds):
    time.sleep(seconds)

async def io_task_async(seconds):
    await asyncio.sleep(seconds)

实测结果:多线程执行 50 个任务耗时 0.11s,asyncio 执行 50 个任务耗时 0.10s------两者在 I/O 密集型场景下的效果基本相当,都能把原本需要 5 秒(50 x 0.1s)的顺序等待压缩到约 0.1 秒。这说明数量不大的时候,选哪个对性能影响不大,真正的差异要看下面的资源开销。

3. 资源开销对比

创建 1000 个线程 vs 1000 个协程,分别用独立进程测量避免互相干扰:

python 复制代码
# 1000个线程
threads = [threading.Thread(target=worker) for _ in range(1000)]
for t in threads: t.start()

结果:耗时 0.045s,峰值内存约 49.5MB

python 复制代码
# 1000个协程
tasks = [asyncio.create_task(coro_worker()) for _ in range(1000)]

结果:耗时 0.001s,峰值内存约 21.7MB

创建协程比创建线程快了 45 倍,内存占用也只有线程的不到一半。这就是为什么面对成百上千甚至上万的并发 I/O 任务(比如同时抓取几千个网页),asyncio 是比 threading 更合适的选择------线程数量一旦上千,内存和调度开销会变得很可观,而协程的轻量级特性可以轻松支撑更大的并发规模。

4. 三者可以混用:run_in_executor

asyncio 代码里如果混进了一段无法避免的 CPU 密集型计算,不应该直接在协程里硬跑(会卡住整个 event loop,Day19 讲过这个坑),而是用 loop.run_in_executor() 把它丢进线程池或进程池:

python 复制代码
async def main():
    loop = asyncio.get_running_loop()
    with ProcessPoolExecutor() as pool:
        cpu_future = loop.run_in_executor(pool, cpu_task)   # 丢进进程池,不阻塞event loop
        await quick_io()   # 这个协程能在CPU任务算的同时正常执行,不被卡住
        result = await cpu_future

实测:quick_io 正常在 0.1 秒内完成,没有被 cpu_task 卡住,cpu_task 在后台的进程池里独立运行------这是三种方案实际项目里最常见的组合方式:用 asyncio 做整体的 I/O 调度框架,遇到 CPU 密集型的小段计算就临时借助线程池/进程池来处理,不需要在三选一里死磕,可以搭配使用。

5. 一张图说清楚怎么选

flowchart TD A[任务是CPU密集型还是IO密集型] -->|CPU密集型| B[用multiprocessing<br/>绕开GIL利用多核] A -->|IO密集型| C{并发数量级} C -->|几十到几百| D[用threading<br/>接口简单够用] C -->|成百上千甚至更多| E[用asyncio<br/>切换开销小更省资源]

四、面试追问

Q1:三种方案各自的核心原理是什么?

threading 是共享内存的操作系统线程,受 GIL 限制,同一时刻只有一个线程能执行 Python 字节码;multiprocessing 是独立内存和独立 GIL 的操作系统进程,能真正利用多核并行;asyncio 是单线程内基于 await 主动让出的协作式调度,本质是并发而不是并行。

Q2:CPU 密集型任务该选哪个,为什么?

选 multiprocessing。实测同样的计算任务,多线程耗时比值 0.97(几乎没有加速),多进程比值 0.57(明显加速),因为多进程能绕开单个 GIL 的限制,让多个进程各自的解释器真正同时执行字节码,利用多核 CPU。

Q3:I/O 密集型任务,线程和协程怎么选?

并发数量不大(几十到几百)时两者性能相当,选哪个都可以;并发数量很大(成百上千甚至更多)时优先选 asyncio------实测创建 1000 个协程比创建 1000 个线程快 45 倍、内存占用不到线程的一半,协程的轻量级特性更适合大规模并发场景。

Q4:三种方案能不能混用?

可以。asyncio 代码里如果有无法避免的 CPU 密集型计算,用 loop.run_in_executor() 把它丢进线程池或进程池处理,这样它不会阻塞 event loop,其他协程可以继续正常执行------这是实际项目中很常见的组合方式,不需要在三者里死磕单选。

Q5:三者的资源开销/可扩展性对比是怎样的?

进程开销最大:独立内存空间,创建成本高,适合数量不多的 CPU 密集型任务;线程开销中等:共享内存,但每个线程仍有独立的调用栈和操作系统调度成本,数量上千会带来明显的内存和调度压力;协程开销最小:创建和切换都只是函数调用级别的成本,可以轻松支撑上万级别的并发数量。


下一篇预告

模块四(GIL 与并发编程)到这里全部完成。Day22 开始进入模块五------内存管理与性能,第一篇讲引用计数与垃圾回收:Python 的内存管理机制到底是怎么工作的。

相关推荐
Three_ST42 分钟前
沐神-动手学习深度学习-习题答案4.4模型选择,欠拟合,过拟合
人工智能·python·深度学习·学习·算法
CodeStats1 小时前
【Java进程通信】Java进程通信系统完全指南:从ProcessBuilder底层原理到多语言实战
java·前端·python·进程·ai编程·processbuilder
程序员cxuan1 小时前
DeepSeek 多模态深度实测,确实有东西的
人工智能·后端·程序员
用户0332126663671 小时前
使用 Python 设置 Excel 文件属性
python
jyOverQ1 小时前
LangGraph Agent 模式详解:从自主决策到 Tool Calling 执行链
python·langchain
青 春 记 忆1 小时前
LeetCode 226. 翻转二叉树|Python 解法详解
python·算法·leetcode
evans在进步1 小时前
Spring Boot 核心机制详解:自动装配、事件监听、异步任务与请求映射
java·spring boot·后端
小女孩真可爱1 小时前
GPT(1)----------从零实现 GPT 模型以生成文本
人工智能·python·gpt·nlp
码云骑士1 小时前
116-Python调用GPT-4V分析图片-Base64编码-多图-JSON结构化输出
开发语言·python·json