Lazy loaded image
鱼皮速通
Lazy loaded imagePython 协程
Words 236Read Time 1 min
2026-6-12
2026-6-12
slug
Python 协程
type
Post
status
Published
date
Jun 12, 2026
tags
推荐
文字
summary
category
鱼皮速通
icon
password

核心 analogy

协程 = 能暂停和恢复的函数。普通函数一旦调用就跑到底;协程跑到一半可以主动"让出"控制权(await),等条件满足再从断点继续。
 
把 event loop 想成一个单线程的调度员:手里攥着一堆协程,谁卡在 I/O(网络、磁盘)就把谁挂起,转去跑别的,等 I/O 好了再回来。所以是 concurrency(并发)不是 parallelism(并行)——始终一个线程,靠"填补等待空隙"提速。
 

为什么需要它

I/O-bound 场景下,线程大部分时间在 (等 LLM 返回、等 DB 查询)。多线程能解决但有 GIL + 线程切换开销 + 锁。协程在用户态切换,没有 OS 线程开销,单线程就能扛上万并发连接。
对你做 Agent 直接相关:并行发多个 tool-call、同时打多个 LLM 请求,本质都是用 asyncio 把等待时间复用起来。
 

技术实现

定义与运行
注意:fetch("a") 本身只是返回一个 coroutine 对象,不执行。必须丢给 event loop(await 它 / 包成 Task / asyncio.run)才真正跑。这是新手最常踩的坑。
串行 vs 并发
gather 就是你做 parallel tool dispatch 的核心原语。
Task:手动调度
create_taskgather 的区别:前者立刻开始执行并返回句柄,后者是"打包 + 等全部完成"。

await 能 await 什么

只能 await awaitable:coroutine、Task、Future。底层是对象实现了 __await__asyncio.sleep 是 awaitable,而 time.sleep阻塞调用——在协程里用 time.sleep 会卡死整个 event loop,所有协程一起停。这是另一个高频翻车点。

历史包袱(面试可能问)

  • Python 3.4:@asyncio.coroutine + yield from,基于 generator
  • Python 3.5+:原生 async/await 语法糖,语义上和 generator coroutine 同源(都靠暂停/恢复),但成了独立类型
所以协程本质是 generator 的泛化:generator 用 yield 把值送出去,coroutine 用 await 把控制权让出去。
 
一个线程跑 event loop,轮询式地问 OS "谁的 I/O 好了"。某协程要做 I/O 时,把 fd( file descriptor) 注册给 OS 就让出,线程转身去 resume 其他就绪协程。等待全程由 OS 负责,线程只在彻底没活时才阻塞在 kqueue 上。
 
协程发起网络请求 → OS 给这个连接分配一个 fd(比如 7 号)→ event loop 把"7 号"登记进 kqueue:"盯着 7 号,它可读了喊我" → 线程撒手去跑别人 → 网卡数据到了,内核标记 7 号就绪 → select() 返回"7 号好了" → loop 凭这个号找到对应协程,resume 它。
 
协程要 I/O → 把 fd 注册给 OS → 协程让出 → 线程转去跑别的协程 → OS 处理完 I/O 标记 fd 就绪 → loop 发现后唤醒(resume)对应协程从断点继续。
 
 
 
 
上一篇
后端架构与存储原理
下一篇
嵌入式开发流程