Rust `await` 与锁选型深度说明:Future 状态机、`tokio::sync`、`parking_lot` 与粒度设计
一篇从执行模型出发的 Rust 并发说明文档,系统解释 await 背后的 Future 状态机、poll/Waker 协作方式、锁为何会在异步语义下变成设计问题,以及 tokio::sync 与 parking_lot 的实现特点、边界与实际选型方法。
Rust await 与锁选型深度说明:Future 状态机、tokio::sync、parking_lot 与粒度设计 Code Version: 基于当前仓库工作树,时间点为 2026-04-01。 讨论范围: 本文解释 Rust async/await 在运行时到底发生了什么、为什么锁在 await 语义下会变成设计问题、tokio::sync 与 parking_lot 的实现特征与边界,以及在 StaticFlow backend 里如何做锁粒度选择。 非目标: 不展开 Tokio runtime 的全部调度实现,也不展开 CPU 缓存一致性和底层 OS futex 细节到源码级别。1. 背景与目标 Rust 后端里“锁怎么选”这个问题,只有在同步程序里才比较简单。 一旦进入 async/await,问题会立刻变成三层叠加:await 会让执行流中断,但并不等于线程阻塞。锁的等待方式可能是“阻塞线程”,也可能是“挂起任务”。一把锁是否安全,不只取决于锁本身,还取决于它在多大粒度上保护数据、持有多久、是否穿过 await。 如果只记一句口诀,通常会变成一种危险的经验主义:“高性能锁就都换成 parking_lot”“async 场景就都用 tokio::sync” 这两句都不够准确。 真正需要理解的是:await 不是神秘语法糖,而是 Future 状态机的挂起点。锁不是“快”或“慢”这么简单,而是“线程级等待”还是“任务级等待”。锁选型的核心不是 crate 名字,而是临界区边界、共享状态模型和调度语义是否匹配。 本文的目标有四个:把 Rust await 的运行模型讲清楚,尤其是 poll、Pending、Waker 和状态机的关系。把“为什么同步锁跨 await 很危险”讲清楚,而不是停留在经验结论。把 tokio::sync 与 parking_lot 的设计特点、优势和限制拆开讲。给出一套实际可用的锁粒度选择方法,并用当前 StaticFlow backend 的真实例子验证。2. 模型与术语 先统一几个术语。 | 术语 | 含义 | 关键点 | |---|---|---| | Future | Rust 异步计算的抽象对象 | 不是线程,不是协程栈,而是可被 poll 的状态机 | | poll | executor 推动 Future 前进的一次尝试 | 返回 Ready(T) 或 Pending | | Waker | Future 告诉 executor “我以后可以继续了”的回调句柄 | 用于在依赖就绪时重新调度任务 | | await | 当前 Future 等待另一个 Future 完成的语法 | 编译后会拆成状态保存 + 子 Future poll | | 任务挂起 | 当前 Future 返回 Pending | 不一定阻塞线程 | | 同步锁 | std::sync / parking_lot 这一类锁 | 等不到锁时阻塞线程 | | 异步锁 | tokio::sync 这一类锁 | 等不到锁时挂起任务,不阻塞 worker 线程 | | 锁粒度 | 一把锁覆盖的数据范围和临界区范围 | 全局锁、分桶锁、每账号锁、每条目锁等 | | 临界区 | 持锁执行的那段逻辑 | 越短越容易推理,也越不容易拖垮并发 | 本文后面会反复用到两个判断:这段代码持锁期间会不会发生 await这段共享状态能不能进一步缩小到更小粒度 这两个问题,比“这个 crate 性能好不好”更重要。3. 端到端执行模型3.1 async fn 到 Future 状态机的转换 Rust 的 async fn 编译后,不会变成一个真正拥有独立栈的协程。 它会变成一个实现了 Future trait 的匿名状态机对象。 逻辑上可以把下面这段代码:async fn load_user() -> Result<User> { let token = read_token().await?; let user = fetch_user(token).await?; Ok(user) } 理解成一个”保存局部变量和当前阶段”的对象。编译器生成的状态机大致等价于:enum LoadUserFuture { /// 初始状态:还没开始 Start, /// 阶段一:正在等 read_token() WaitingReadToken { sub_future: ReadTokenFuture, }, /// 阶段二:拿到 token,正在等 fetch_user(token) WaitingFetchUser { token: Token, // 跨 await 保留的局部变量 sub_future: FetchUserFuture, },…
正在初始化 WebAssembly 引擎…
首次编译原生模块可能需要数秒
就绪后,页面交互将以接近原生的速度运行