协程
**协程(Coroutine)**是一种用户态的轻量级并发单元,由程序自身控制调度(而非操作系统)。Python 3.5+ 通过 async/await 语法原生支持协程,标准库 asyncio 提供完整的事件循环和工具集。
协程 vs 线程 vs 进程
| 维度 | 协程 | 线程 | 进程 |
|---|---|---|---|
| 调度者 | 用户程序 | 操作系统 | 操作系统 |
| 内存开销 | 极小(几 KB) | 较大(MB 级) | 最大(独立地址空间) |
| 切换成本 | 极低(函数调用级) | 较高(内核态切换) | 最高 |
| 并行能力 | 无(单线程) | 受 GIL 限制 | 真正并行(多核) |
| 适合场景 | I/O 密集,海量连接 | I/O 密集,遗留同步代码 | CPU 密集 |
async/await 基础
import asyncio
async def greet(name: str, delay: float) -> str:
"""async def 定义协程函数,调用后返回协程对象(不会立即执行)。"""
print(f"{name} 开始等待 {delay}s")
await asyncio.sleep(delay) # await 挂起当前协程,让出事件循环
return f"Hello, {name}!"
async def main() -> None:
# 串行执行:总耗时 1+2 = 3s
r1 = await greet("Alice", 1.0)
r2 = await greet("Bob", 2.0)
print(r1, r2)
asyncio.run(main()) # Python 3.7+,创建事件循环并运行并发执行
await 本身是串行的;要实现并发,需要用 gather 或 create_task:
import asyncio
import time
async def fetch(name: str, delay: float) -> str:
await asyncio.sleep(delay)
return f"{name} done"
async def main() -> None:
start = time.perf_counter()
# gather:并发运行,总耗时约 max(1, 2, 1.5) ≈ 2s
results = await asyncio.gather(
fetch("任务A", 1.0),
fetch("任务B", 2.0),
fetch("任务C", 1.5),
)
print(results)
print(f"总耗时: {time.perf_counter() - start:.2f}s")
asyncio.run(main())任务与取消
import asyncio
async def long_task(n: int) -> int:
await asyncio.sleep(n)
return n
async def main() -> None:
# create_task 立即提交到事件循环
task = asyncio.create_task(long_task(5))
await asyncio.sleep(2)
task.cancel() # 取消任务
try:
await task
except asyncio.CancelledError:
print("任务已取消")
asyncio.run(main())协程的优缺点
优点
- 切换开销极小(用户态切换,不涉及内核)。
- 单线程内实现高并发,不需要锁(共享状态在同一线程内,
await点是唯一切换时机)。 - 可以支撑数万乃至数十万个并发连接(如 Web 服务器)。
缺点
- 本质是单线程,无法利用多核 CPU(CPU 密集型任务用
ProcessPoolExecutor)。 - 一旦某个协程执行了阻塞的同步 I/O(如
time.sleep),会阻塞整个事件循环。 - 必须全链路使用
async/await,混入同步阻塞调用需要用loop.run_in_executor()。
在协程中调用阻塞函数
import asyncio
from concurrent.futures import ThreadPoolExecutor
def blocking_read(path: str) -> str:
"""同步阻塞的文件读取(遗留代码)。"""
with open(path, encoding="utf-8") as f:
return f.read()
async def main() -> None:
loop = asyncio.get_event_loop()
# run_in_executor 把阻塞调用放到线程池中,不阻塞事件循环
with ThreadPoolExecutor() as pool:
content = await loop.run_in_executor(pool, blocking_read, "README.md")
print(content[:100])
asyncio.run(main())历史背景(了解)
Python 协程经历了多个演变阶段:
| 阶段 | 技术 | 特点 |
|---|---|---|
| Python 2 | generator(yield) | 手动切换,无标准调度 |
| Python 2/3 | greenlet(第三方) | 显式 switch(),需手动调度 |
| Python 2/3 | gevent(第三方) | 基于 greenlet + monkey patch 自动切换 |
| Python 3.4 | asyncio(标准库) | 基于 yield from,事件循环标准化 |
| Python 3.5+ | async/await(当前标准) | 语法清晰,生态完整 |
当前项目应直接使用 async/await + asyncio,greenlet 和 gevent 仅出现在遗留代码中。
最后更新于