.
总结与延伸阅读
. 总结与延伸阅读
串联 Rust 异步机制从编译期变换到运行期调度的全景图,汇总核心术语与延伸资料.
回顾这五篇文章,Rust 的
async
async
/
await
await
将编译期代码变换、所有权与类型约束以及库级事件循环三者结合在一起:
- 资源与模型选择(
为什么需要异步
): 为了在不引入强制运行时和动态栈管理的前提下支持高并发等待,Rust 放弃了有栈协程与基于回调推送的模型,选择了基于轮询拉取的无栈协程. 这使得嵌套异步调用能够内联为单一状态机结构体,并以直接
dropdrop状态机的方式免费获得了同步取消能力. - 轮询与唤醒契约(
Future 与 Waker
): 标准库用
Future::pollFuture::poll和Poll<T>Poll<T>规定了推进状态机的统一接口,用ContextContext与WakerWaker解耦了“驱动状态机”和“监听外部事件”.RawWakerRawWaker与RawWakerVTableRawWakerVTable通过手工构造的双字长胖指针,在不依赖堆分配和泛型传染的条件下实现了跨执行器的类型擦除. - 编译期状态机变换(
async/await 脱糖
): 编译器按
.await.await挂起点切分控制流,分析跨越挂起点存活的局部变量,将顺序代码改写为实现了FutureFuture的带标签联合体(enumenum). 理解这一内存布局,就能直接推导状态机的体积变化、递归async fnasync fn的间接层需求,以及跨.await.await持有非SendSend锁守卫带来的编译错误. - 自引用安全边界(
Pin 与自引用结构
): 当局部变量借用跨越
.await.await时,状态机内部会出现指向自身其他字段的自引用指针.Pin<P>Pin<P>通过在类型层面限制安全代码获取!Unpin!Unpin对象的&mut T&mut T,阻止了在首次pollpoll之后通过mem::swapmem::swap按位搬移状态机,从而在零运行时开销下守住了内存安全. - 运行时事件闭环(
执行器与反应器
): 执行器从就绪队列取出任务并调用
pollpoll;叶子FutureFuture遇到WouldBlockWouldBlock时向反应器注册WakerWaker并返回PendingPending;操作系统通过epollepoll/kqueuekqueue/IOCPIOCP通知反应器事件就绪后,反应器调用Waker::wake()Waker::wake()将任务重新排入执行器队列.
我们可以把从“写下一行
async fn
async fn
”到“在操作系统线程上跑完一次非阻塞 I/O”的全链路浓缩为下面这张全景图:
1 术语对照
文中出现的英文术语与中文译名,同样收录于全站 术语对照 中:
- 异步编程
- asynchronous programming
- 绿色线程
- green thread
- 有栈协程
- stackful coroutine
- 无栈协程
- stackless coroutine
- 分段栈
- segmented stack
- 栈拷贝
- stack copying
- 协作式调度
- cooperative scheduling
- 抢占式调度
- preemptive scheduling
- 半协作式取消
- semi-cooperative cancellation
- 取消安全性
- cancellation safety
- 拉取模型
- pull-based model
- 推送模型
- push-based model
- 语域
- register
- 挂起点
- suspension point
- 状态机
- state machine
- 轮询
- poll
- 唤醒器
- waker
- 伪唤醒
- spurious wakeup
- 自引用结构
- self-referential struct
- 地址固定
- pinning
- 固定类型状态
- pinned typestate
- 析构保证
- drop guarantee
- 结构化固定投影
- structural pin projection
- 执行器
- executor
- 反应器
- reactor
2 延伸阅读
关于 Rust 异步机制的设计历史、标准库规范与工程实践,可进一步查阅以下资料:
Without Boats 的个人博客 Without boats, dreams dry up:作为 2017–2019 年间主导 Rust
asyncasync/awaitawait与PinPin设计的核心作者,他在博客中系统记录了整套机制的来龙去脉与后续演进思考.其中与本系列直接相关的文章包括:- Why async Rust?:回顾 Rust 1.0 前移除绿色线程的原因、从内部迭代器到外部迭代器的转变如何启发了基于轮询的
FutureFuture(见 为什么 Rust 在 1.0 前移除了绿色线程 ). - Pin 与 Pinned places:解释跨
.await.await借用如何产生自引用状态机、为什么移动构造函数与?Move?Move特型在 Rust 中不可行,以及怎样通过“固定位置”(pinned places)消除PinPin作为纯库类型带来的可用性断崖(见 固定类型状态与析构保证 与 自引用结构的替代方案与 Pin 的可用性断崖 ). - The registers of Rust 与 poll_next:提出控制流作用的四个语域(核心、消费、组合子、控制流),并从状态机数量、内存固定与动态分发的角度论证为什么
AsyncIteratorAsyncIterator应当采用poll_nextpoll_next而非async fn nextasync fn next(见 控制流作用的四个语域与迭代–异步对偶 ). - Asynchronous clean-up:剖析半协作式取消、
select!select!循环中的取消安全性陷阱,以及隐式异步析构(AsyncDropAsyncDrop)面临的对象安全与类型传染障碍(见 取消安全性与异步清理的困境 ).
- Why async Rust?:回顾 Rust 1.0 前移除绿色线程的原因、从内部迭代器到外部迭代器的转变如何启发了基于轮询的
- Ralf Jung 的形式化文章 A Formal Look at Pinning:在 RustBelt 框架下给出独占(owned)、共享(shared)与固定(pinned)三种类型状态及析构保证的形式化刻画.
- Rust 官方工作组维护的 Asynchronous Programming in Rust(《Rust 异步编程》),介绍
FutureFuture、WakerWaker、PinPin以及单线程执行器的基础实现. - 标准库文档中的
std::pinstd::pin模块文档与std::taskstd::task模块文档,给出PinPin的析构保证与结构化投影的精确安全条件. - Phil Oppermann 的 Writing an OS in Rust: Async/Await,展示如何在没有任何操作系统支持的裸机内核中,用硬件中断、无锁队列与 Rust 的
asyncasync/awaitawait搭出一套协作式多任务系统. - Alice Ryhl 的工程文章 Async: What is blocking?,讨论协作式调度下“阻塞”的定量边界以及
spawn_blockingspawn_blocking与rayonrayon的选型准则.