为什么需要异步
一、为什么需要异步?——同步的瓶颈
1.1 先看问题——同步代码在等什么?
# 同步版:下载 10 个 URL —— 一个卡住,后面全等
import time
import requests
def download(url):
resp = requests.get(url) # ① 发请求 → 等网络返回(这期间 CPU 闲置!)
return resp.text
start = time.time()
for url in urls: # ② 串行:每个 URL 都要等上一个下载完
data = download(url)
print(f"耗时: {time.time() - start:.1f}s") # 10 个 URL → ~10s(每个 1s)
时间线(同步):
URL1: [====网络等待 1s====]
URL2: [====网络等待 1s====]
URL3: [====网络等待 1s====]
↑ 每个请求期间 CPU 闲置——浪费!总时间 = 1s × 10 = 10s
CPU 和 I/O 的速度差距(数量级):
| 操作 | 耗时 | 相当于 |
|---|---|---|
| CPU 执行一条指令 | ~0.3 ns | 1 秒 |
| 内存读取 | ~100 ns | 5 分钟 |
| 网络请求(同机房) | ~1 ms | 1 个月 |
| 磁盘读取 | ~10 ms | 1 年 |
| 网络请求(跨洲) | ~100 ms | 10 年 |
一句话:I/O 在等的时候,CPU 完全可以干别的。同步代码的问题是——它在 I/O 等待期间把整个线程堵死了。
1.2 异步怎么做——“等待时切走”
时间线(异步):
URL1: [发请求][··········等网络··········][收数据]
URL2: [发请求][··········等网络··········][收数据]
URL3: [发请求][··········等网络··········][收数据]
↑ 等网络期间切去发其他请求——总时间 ≈ 1 个请求的时间!
# ✅ 异步版:同时下载 10 个 URL —— 总耗时 ≈ 最慢的那个
import asyncio
import aiohttp
async def download(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp: # ① await 处暂时"挂起"
return await resp.text() # ② 等待期间,事件循环去执行其他协程
async def main():
tasks = [download(url) for url in urls]
results = await asyncio.gather(*tasks) # ③ "并发"执行——实际是快速切换
asyncio.run(main()) # 10 个 URL → ~1s
关键区分:异步 ≠ 并行。异步是”一个线程在多个任务间快速切换”,并行是”多个线程同时在多个 CPU 核上跑”。asyncio 做的是前者。
1.3 什么时候用异步?——I/O 密集型 vs CPU 密集型
| I/O 密集型 | CPU 密集型 | |
|---|---|---|
| 典型场景 | Web 请求、数据库查询、文件读写、消息队列 | 数学计算、图像处理、加密解密、ML 推理 |
| CPU 状态 | 大部分时间在等 I/O——CPU 闲置 | 大部分时间在算——CPU 跑满 |
| asyncio | ✅ 最佳场景 | ❌ 没用——没 I/O 可等 |
| 多线程 | ✅ 能用(但重) | ⚠️ Python 有 GIL,用不了多核 |
| 多进程 | ❌ 杀鸡用牛刀 | ✅ 最佳——绕过 GIL,用满多核 |
一句话讲清:“asyncio 解决的是 I/O 密集型并发——大量时间花在等待网络/磁盘,CPU 闲置。如果是 CPU 密集型,Python 应该用多进程绕开 GIL。“
速记卡(面试闪卡)
Q1:一句话讲清「为什么需要异步」到底是什么?
A:异步是在 I/O 等待时把线程”切走”去干别的事——同步代码在等网络/磁盘时把整个线程堵死。
Q2:一、同步的瓶颈 —— 怎么理解? —— 怎么理解?
A:同步下载 10 个 URL 是一个等完下一个,CPU 在等网络时全闲置。CPU 执行一条指令约 0.3ns,网络请求同机房约 1ms——差 6 个数量级。就像你烧水时傻站着等,水开前啥也不干。
Q3:二、异步怎么做:等待时切走 —— 怎么理解? —— 怎么理解?
A:异步用 await 在等待处”挂起”协程,事件循环转去执行别的协程,等网络回来了再回来。10 个 URL 总耗时≈最慢那个。好比烧水时去拖地,水开了再来关火——时间重叠了。
Q4:三、异步 ≠ 并行 —— 怎么理解? —— 怎么理解?
A:异步是”一个线程在多个任务间快速切换”(并发),并行是”多个线程在多核上同时跑”。asyncio 做前者:单线程靠协程让出 CPU。就像一个人同时盯多锅菜,不是多个人各看一锅。
Q5:四、I/O 密集 vs CPU 密集 —— 怎么理解? —— 怎么理解?
A:I/O 密集型(Web 请求、查库、读写文件)大部分时间在等,CPU 闲置——asyncio 最佳。CPU 密集型(计算、图像处理)CPU 跑满,异步没用,得用多进程绕开 GIL。选型看瓶颈在”等”还是”算”。
Q6:核心速记主线有哪些?
-
同步:I/O 等待时线程被堵死,CPU 闲置
-
异步:await 挂起协程,事件循环切去干别的
-
异步是并发(单线程切换),不是并行(多核同时)
-
I/O 密集用 asyncio,CPU 密集用多进程
口诀
A:同步等待线程挡,
异步切走不空忙;
并发切换非并行,
IO 密集它最强。