想象你是一家高档餐厅的服务员。传统方式下,你接到顾客A的点餐后,需要一直等在厨房,直到菜品做好才能去服务顾客B。这显然效率很低。
聪明的服务员会这样做:接到顾客A的订单后,把单子交给厨房,然后立即去服务顾客B、C、D…当厨房通知某个菜做好了,再去取餐送给相应的顾客。
这就是事件循环的工作方式——不傻等,而是充分利用等待时间去做其他事情。
事件循环(Event Loop)是一个无限循环的程序,它的核心工作就是:
while True:
# 1. 检查有哪些任务可以执行
# 2. 执行这些任务
# 3. 如果任务需要等待(如网络请求),就切换到其他任务
# 4. 检查之前等待的任务是否完成
# 5. 重复以上过程
看一个简单的对比:
# 同步方式:总耗时 = 2 + 3 + 1 = 6秒
def sync_download():
download_file_1() # 耗时2秒
download_file_2() # 耗时3秒
download_file_3() # 耗时1秒
# 异步方式:总耗时 ≈ max(2, 3, 1) = 3秒
async def async_download():
await asyncio.gather(
download_file_1(),
download_file_2(),
download_file_3()
)
关键点:Python的协程运行在单线程中!
这意味着:
async def fetch_weather(city):
print(f"开始查询{city}的天气...")
# await 是让出控制权的关键时刻
# 在这里,当前任务会被挂起
response = await http_get(f"/weather/{city}")
# 当I/O完成后,任务从这里恢复执行
print(f"{city}的天气是:{response}")
return response
让我们通过一个实际例子来理解:
import asyncio
import time
async def task1():
print("Task1: 开始执行")
await asyncio.sleep(2) # 模拟I/O操作
print("Task1: 执行完成")
async def task2():
print("Task2: 开始执行")
await asyncio.sleep(1)
print("Task2: 执行完成")
async def main():
t1 = asyncio.create_task(task1())
t2 = asyncio.create_task(task2())
await t1
await t2
asyncio.run(main())
执行流程图:
import asyncio
from aiohttp import web
import time
# 模拟数据库查询
async def fetch_from_db(user_id):
print(f"[DB] 开始查询用户{user_id}")
await asyncio.sleep(0.1) # 模拟I/O延迟
print(f"[DB] 用户{user_id}查询完成")
return {"id": user_id, "name": f"User{user_id}"}
# 模拟外部API调用
async def fetch_from_api(user_id):
print(f"[API] 开始调用API获取用户{user_id}分数")
await asyncio.sleep(0.2) # 模拟网络延迟
print(f"[API] 用户{user_id}API调用完成")
return {"score": user_id * 100}
# 处理请求
async def handle_user(request):
start = time.time()
user_id = int(request.match_info['user_id'])
print(f"\n=== 处理请求:用户{user_id} ===")
# 并发执行多个I/O操作
# 关键:这两个操作会同时进行!
db_data, api_data = await asyncio.gather(
fetch_from_db(user_id),
fetch_from_api(user_id)
)
result = {**db_data, **api_data}
print(f"=== 请求完成:耗时{time.time()-start:.3f}秒 ===\n")
return web.json_response(result)
解决方案:
# 错误:阻塞事件循环
async def bad_cpu_intensive():
result = 0
for i in range(10**8):
result += i
return result
# 正确:定期让出控制权
async def good_cpu_intensive():
result = 0
for i in range(10**8):
result += i
if i % 10**6 == 0: # 每100万次计算
await asyncio.sleep(0) # 让出控制权
return result
# 最佳:使用线程池
async def best_cpu_intensive():
loop = asyncio.get_event_loop()
result = await loop.run_in_executor(
None, # 使用默认线程池
lambda: sum(range(10**8))
)
return result
graph TB
subgraph 1000个线程
M1[内存: ~2GB]
C1[上下文切换: 频繁]
L1[需要锁: 是]
S1[调度: 内核调度]
end
subgraph 1000个协程
M2[内存: ~5MB]
C2[上下文切换: 轻量]
L2[需要锁: 否]
S2[调度: 用户态调度]
end
style M1 fill:#f99,stroke:#333,stroke-width:2px
style M2 fill:#9f9,stroke:#333,stroke-width:2px
mindmap
root((异步编程哲学))
单线程模型
任何时刻只执行一个任务
必须主动让出控制权
协作式而非抢占式
高效利用等待
I/O等待时切换任务
不浪费CPU周期
充分利用硬件资源
简化并发模型
无需锁和同步
避免竞态条件
代码更易理解
事件驱动
响应式编程
非阻塞I/O
高并发处理
因为是单线程,所以必须让出控制权!
这是理解Python异步编程的核心。每个await
都是一次主动的让步,让其他任务有机会执行。没有await
,就没有并发。
随着现代应用的发展,异步编程变得越来越重要:
事件循环就像一个高效的调度员,在单线程的舞台上,指挥着成千上万个协程演员。每个await
都是一次优雅的退场,让其他演员有机会登台表演。
这种看似受限的单线程模型,通过巧妙的设计,反而实现了比多线程更高的性能。这就是异步编程的魅力所在——限制带来自由,约束创造效率。
下次当你使用async/await
时,记住:你正在一个单线程的世界里,创造着并发的奇迹。