Rust中的异步编程
通识内容
异步和多线程
异步即在不阻塞其他任务执行的情况下,并发执行多个任务,且可以在仅有几个工作线程或仅有主线程下使用。多线程则是不同的线程执行不同的任务,
每个线程各司其职,或有线程间的上下文切换,简单来说:异步多数用在大量IO并发时,而多线程更适合于有大量cpu密集型任务时使用(配合线程池)
线程用于并行工作,异步用于并行等待
rust中的异步模型
rust中天然支持异步编程,使用await,async关键字,将普通函数转变为异步函数,并通过tokio crate 的runtime环境进行轮询,
切换和执行,让异步编程写起来与同步代码几乎无差别。
rust中异步模型的概述
rust中的异步模型,实际上是使用future(能产出值的异步计算),通过轮询,挂起,唤醒,执行等操作来实现异步,runtime依赖tokio。
什么是Tokio
Tokio是一个Rust 异步运行时库,底层基于epoll/kqueue这样的跨平台多路IO复用以及event loop,目前正在支持io_uring。其线程会执行task,可以充分利用多核,而Task 线程是Rust基于Future抽象出的一种绿色线程,因为不需要预先分配多余的栈内存,可以创建大量的Task,很适合做IO密集型应用。
tokio runtime的任务流转
关键点:Future中的poll,Reactor ,Executor,Waker,Pending,Ready,PIN,!Unpin
1.async函数会在await时生成Future,并被主动轮询一次,如果其执行完毕,则状态被标记为Ready并在调用处即返回其值,并结束。
2.若没有执行结束,则被标记为Pending并会被执行器推入任务执行队列,另一方面则会挂载到events上,如epoll的红黑树等待Waker
3.当有Waker Ready时则会激活poll event(即所有被Waker 的任务),轮询(Waker events)并依次执行,执行成功的任务会由Pending转换为Ready,进而执行完毕从任务侧去除。
4.而未能执行完毕的任务则会继续留存于events中,等待下一次的Waker唤醒。
5.直到所有任务全部执行完成。
Executor执行器会管理一批 Future (最外层的 async 函数),然后通过不停地 poll 推动它们直到完成。 最开始,执行器会先 poll 一次 Future ,后面就不会主动去 poll 了,而是等待 Future 通过调用 wake 函数来通知它可以继续,它才会继续去 poll 。这种 wake 通知然后 poll 的方式会不断重复,直到 Future 完成。
即执行器负责执行最外层async函数,或主动一次推动,或被动Waker唤醒,Poll则是具体的推动某一个Future的执行。
从代码实现角度看Rust中的异步(Tokio)
Rust中的Future定义:
trait Future {
type Output;
fn poll(
// 首先值得注意的地方是,`self`的类型从`&mut self`变成了`Pin<&mut Self>`:
self: Pin<&mut Self>,
// 其次将`wake: fn()` 修改为 `cx: &mut Context<'_>`:
cx: &mut Context<'_>,
) -> Poll<Self::Output>;
}
1.Output: 异步函数的返回值类型
2. Future推动者
1) Pin<&mut Self>,固定其在内存中的内存地址(为避免自引用类型在Future被移动时失效)
2) &mut Context<'_>,通过提供一个Waker类型的值,即可唤醒特定的任务执行
3) PollSelf::Output 返回值类型。
pin的主要作用即将一个数据值pin在内存的固定位置,标记过unpin的数据,即便使用了Pin依然无法固定内存地址。
pin对应!unpin,在Rust中,开发者熟知的数据类型均默认实现了unpin。Pin是一个struct,而unpin是一个trait
#[tokio::main]的作用是什么
- rust 的main函数可以是异步函数吗?
不可以,rust中的异步是惰性的,被动的,而main函数是需要主动执行的。 - tokio是一个过程宏,tokio runtime需要在主要功能启动之前就启动,然后使用这个异步运行时来控制整个应用程序的运行。
我们来看一些其真面目:
过程宏可以用编译器扩展宏,可以使用如下方式:
cargo install cargo-expand
通常我们在开发环境中使用的都是stable版本,而cargo-expand依赖nightly编译器来扩展宏,所以还需要自己安装nightl编译器。
rustup toolchain install nightly --allow-downgrade
最后我们通过命令行展开真正的main代码块:
cargo +nightly expand
展开后代码如下:
fn main() -> std::io::Result<()> {
...
tokio::runtime::Builder::new_multi_thread().enable_all().build().expect("Failed building the Runtime").block_on(...)
}
#[tokio::main]任务给了我们一种能够定义异步main函数的错觉,而在实际的原理中,它只是获取了异步main函数体,并且补充了必要的样板代码
,让它可以在tokio上运行。
参考文章
[1] https://github.com/sunface/tokio-course/blob/master/src/async.md
[2] https://course.rs/advance/async/future-excuting.html
[3] https://rustmagazine.github.io/rust_magazine_2021/chapter_12/monoio.html
[4] https://tokio.rs/tokio/tutorial
[5] https://course.rs/advance/async/pin-unpin.html
如有勘误,多谢指正。