Skip to content

异步编程 asyncio

事件循环、async/await、Task、信号量与锁、与线程的对比。

Updated View as Markdown
For humans

异步编程 asyncio

asyncio 是 Python 高并发 IO 的标准方案,也是 FastAPI 的底层。面试主线:事件循环怎么转、async/await 是什么、怎么控制并发。

协程与事件循环

事件循环: IO 等待时切换协程

  • 协程async def 定义的函数,调用不执行,返回协程对象
  • 事件循环:单线程调度器,管理所有协程;协程 await 时让出控制权,IO 就绪后恢复
  • await:挂起当前协程直到结果就绪。await 的对象必须是可等待的(协程、Task、Future)
async def fetch(url):
    data = await http_get(url)   # 挂起, 循环去跑别的协程
    return data

async def main():
    tasks = [asyncio.create_task(fetch(u)) for u in urls]
    results = await asyncio.gather(*tasks)   # 并发执行

Task 与并发

工具 作用
asyncio.create_task 把协程包装成 Task 立即调度,不等待结果
asyncio.gather 并发运行多个协程,等全部完成
asyncio.wait_for 超时控制
asyncio.Semaphore 限并发数(信号量)
asyncio.Lock 临界区保护
sem = asyncio.Semaphore(10)      # 最多 10 个并发

async def limited(url):
    async with sem:
        return await fetch(url)

信号量是高频考点:批量任务要限并发(防止打爆下游),async with sem 是标准写法。

与线程的对比

维度 线程 协程
调度 操作系统抢占式 事件循环协作式
切换代价 内核切换,重 函数调用级,轻
并发规模 千级 十万级(内存占用小)
数据竞争 有(要锁) 单线程内无竞争
阻塞调用 不阻塞其他线程 阻塞整个事件循环

协程的代价:任何阻塞调用(time.sleep、同步 IO、CPU 计算)都会卡住整个事件循环,所有协程停摆。解决办法:

  • 同步库调用放 asyncio.to_thread / run_in_executor(丢线程池)
  • 用异步库(httpx.AsyncClient、asyncpg)代替同步库
  • 重计算单独进程

面试追问

  1. async/await 怎么工作? async def 定义协程,await 挂起让出控制权,事件循环调度,IO 就绪恢复。协作式
  2. create_task 和 gather 的区别? create_task 创建任务立即调度;gather 并发等待全部完成。gather 内部也建 Task
  3. 信号量干什么? 限并发数。async with sem 保护,防止一次性打爆下游
  4. 协程里能调 time.sleep 吗? 不能,会阻塞整个事件循环。用 await asyncio.sleep,同步阻塞调用丢 to_thread
  5. 协程和线程怎么选? 高并发 IO 用协程(十万级),需要真并行 CPU 用进程,简单 IO 任务线程也够
Navigation

Type to search…

↑↓ navigate↵ selectEsc close