异步历史
回调函数 -> promise -> async/await
async
/await
async
是一个修饰符,它可以应用在函数上,在调用时立即返回一个 Future
对象。这个 Future
对象最终将给出这个函数的实际返回结果。
async 将一个代码块转化为实现了 future 特征的状态机。
await
用在执行一个 async
的函数。
let user = db.get_user("withoutboats").await;
impl Database {
async fn get_user(&mut self, user: &str) -> User {
let sql = format!("select FROM users WHERE username = {}", user);
let db_response = self.query(&sql).await;
User::from(db_response)
}
}
设计原理
零成本抽象
零成本抽象是 Rust 比较独特的一项准则。
零成本抽象意味着你不使用的东西,你不用为它付出任何代价,进一步讲,你使用的东西,你无法写出比这更好的代码。
绿色线程方案
基于语言运行时,该模型的典型应用是 golang
,它的绿色线程被称为 goroutine
。创建成本很低,可以同时运行成千上万个。
优点:避免线程的上下文切换,开销低。
缺点:集成于语言中,不得不使用该绿色线程,引入调度 goroutine
的运行时成本。
future
方案
基于库的解决方案,为异步 I/O 提供良好的抽象,它不是语言的一部分,也不是每个程序附带的运行时的一部分,只是可选的并按需使用的库。
Future
表示一个尚未得出的值,可以在它被解决(resolved)以得出那个值之前对它进行各种操作。Future
可以避免空的轮询。
基于轮询的 future
future 结构
pub enum Poll<T> {
Ready(T),
Pending,
}
pub trait Future {
type Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}
Runtime
Runtime
由两部分组成, Executor
和 Reactor
。
Executor
为执行器,没有「任何阻塞的等待」,循环执行一系列就绪的 Future
,当 Future
返回 Pending
时,会将 Future
转移到 Reactor
上等待进一步唤醒。
Reactor
为反应器,轮询并唤醒挂载的事件,执行对应的 wake
方法,同时将 Future
放到 Executor
的队列中等待执行。
一句话概括 Runtime
,Future
不能马上返回值的时候,会被交给 Reactor
,Future
的值准备就绪后,调用 wake
传递给 Executor
执行,反复执行,直至整个 Future
返回Ready
。
rust 异步使用
async fn foo() -> {
...
}
将函数的原型返回一个 Futrue
,大致相当于
fn foo() -> impl Future<Output = ()> {
async { ... }
}
async/await
通过一个状态机来控制代码的流程,配合executor
完成协程的切换。
示例
use async_std::task;
use futures::executor::block_on;
use std::{fmt::Debug, time};
async fn hello_world() {
println!("hello, world!");
}
#[derive(Debug)]
struct Song;
async fn learn_song() -> Song {
println!("学习唱歌");
// 模拟IO阻塞等
// task::sleep(time::Duration::from_secs(1)).await; // 不能使用 thread::sleep
Song
}
async fn sing_song(song: Song) {
task::sleep(time::Duration::from_secs(5)).await;
println!("唱歌中... {:?}", song);
}
async fn dance() {
println!("跳舞中...")
}
// 并行任务1:
async fn learn_and_sing() {
// 在唱歌之前等待学歌完成
// 这里我们使用 `.await` 而不是 `block_on` 来防止阻塞线程,这样就可以同时执行 `dance` 了。
let song = learn_song().await;
sing_song(song).await;
}
async fn async_main() {
let f1 = learn_and_sing();
let f2 = dance();
// `join!` 类似于 `.await` ,但是可以等待多个 future 并发完成
// 如果学歌的时候有了短暂的阻塞,跳舞将会接管当前的线程,如果跳舞变成了阻塞
// 学歌将会返回来接管线程。如果两个futures都是阻塞的,
// 这个‘async_main'函数就会变成阻塞状态,并生成一个执行器
futures::join!(f1, f2); // f1, f2 并行完成
}
fn main() {
let future = hello_world(); // Nothing is printed
println!("调用了异步函数 hello_world,此句应该先打印");
block_on(future);
// 测试二
println!("===顺序执行===");
let song = block_on(learn_song());
block_on(sing_song(song));
block_on(dance());
println!("===并行执行===");
block_on(async_main());
}