The reason that this method takes self: Pin<&mut Self> instead of the normal &mut self is that future instances created from async/await are often self-referential, as we saw above. By wrapping Self into Pin and letting the compiler opt-out of Unpin for self-referential futures generated from async/await, it is guaranteed that the futures are not moved in memory between poll calls
Language-supported implementations of cooperative tasks are often even able to backup up the required parts of the call stack before pausing. As an example, Rust's async/await implementation stores all local variables that are still needed in an automatically generated struct
Rust中的状态机实现
我概括一下,引入 Future 必须解决自引用被swap的问题
Pin通过 提供 &Self 来让编译器进行入参检查,mem::swap(& mut self) 需要 mut 类型,所以编译器类型检查失败而退出
https://rust-lang.github.io/async-book/04_pinning/01_chapter.html
Future 任务会跨越线程执行,我们知道Future可以编译成状态机yield执行,那在从Future Pending 到 Done 的过程中需要将全部的状态统一保存起来。
这里最好的办法是 struct,也即 async 中所有的变量都在 struct 上分配,并通过self引用。
整个 Future 执行期间全部的变量都通过 enum 进行保存。
Future 是如何保存执行状态的?
编译之前
编译之后
比如如下async代码块
下面是desugar的代码
会被rustc编译成Future对象,并且跨.await的变量被封装到struct保存,通过生成state维护状态机的状态
并不是全部的local variable 都会被保存,rustc只会保存await需要的变量,这些变量特点是await返回后就会被销毁,所以需要保存到struct
https://discord.com/channels/500028886025895936/500336333500448798/854955299522084914
由于保存变量状态, poll 中获得这状态通过变量引用访问,所以struct往往会保存变量的引用,这就使得 future 不能被移动,所以编译器会为生成的 future 状态机增加 PhantomPinned,使得 future 是 !Unpin
https://os.phil-opp.com/async-await/#pinning-and-futures
https://www.reddit.com/r/learnrust/comments/fkvr15/why_cant_an_async_block_or_fn_implement_unpin/
也正是由于 future !Unpin,为了再次 poll 这个future,必须重新 Pin 住
所以 shadowsocks-rust 这里重新Pin住
https://github.com/willdeeper/shadowsocks-rust/blob/6ff4f5b04b8e22d36cd54477cfcadf8cb403de21/bin/sslocal.rs#L463
虽然看着 server 是 stack local,但 async 会将其保存到 struct,确保pin的整个过程中server始终存在。
https://os.phil-opp.com/async-await/#stack-pinning-and-pin-mut-t
Future 可能会出现自引用结构,比如
https://os.phil-opp.com/async-await/#self-referential-structs
,在await
之后获取了array的引用。这种情况下future move会出问题https://os.phil-opp.com/async-await/#pinning-and-futures
Rust没找到在线编辑器看编译后的代码,但stackless理论中,js和rust最接近,后面不不明白,可以看js编译后是如何利用函数闭包保存状态的
js async编译后的状态机
为什么说 Pin 是零开销?
Pin! 的内容会分配到堆上防止移动,对于自引用结构为了防止移动一定会内存分配到堆上,零开销的意思是不会进行多余的堆分配。
mem::swap 要求 参数为
&mut T
,只要我们保证不泄漏出 mut 就可保证用户代码不会 move 内存造成不安全编程除非显式 !Unpin, 否则编译器会自动实现Unpin,也就是Rust里默认变量是可以移动的
比如下面这代码 https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=636b71d4acef346a9ce810b735908cb1
https://stackoverflow.com/a/49917903/7529562
Javascript Promise 和 Rust Future 的不同
JavaScript 的 Promise 调用可能会返回新的 Promise,而Rust的Future只会返回 Pending 或 Ready,并不会返回新的 Future
Future在Rust中是块写法
这 async 编译为 Future,Future::poll返回
Pending / Ready
,想孵化新的异步任务需调用运行时提供的孵化方法,而不是通过返回 FutureJS中的状态机
我将用 Javascript 来演示 async 如何实现。 async 原理类似 C# 的 async Task,Rust中的 ?. Future
async 本质是依赖 回调函数
又或者下面从babel拷贝过来的代码