Rust异步学习记录
前言
Rust的异步初学起来还是挺懵的,因为现在属于早期,各种新旧版本的类库都在网上,譬如tokio_threadpool这个库的已经废弃,分离到tokio的其他模块了,然后查文档找不着北;于是学起来就各种用法用法扑面而来呼到脸上,只剩一脸懵逼。
我主要从使用角度说说我的Rust异步体验吧,本人菜鸡,欢迎提意见。
概念
首先是几个概念,Poll,Future,Waker,Executor,async/await,Task,Reactor,Stream,Sink。
Poll & Future
这俩一起说,Poll可以当作一个枚举型,表示当前状态已经完成或者正在处理中;Future是一个trait,要求有一个poll方法,返回Poll类型,表示当前执行状态。可以简单当作下面的样子。
enum Poll<T> {
Ready(T),
Pending,
}
trait Future {
type Output;
fn poll(&mut self, wake: fn()) -> Poll<Self::Output>;
}
这里面的wake方法跟Waker类型关联,实际上Future的实现这里是传的封装了Waker实例的Context类型。
Task & Waker & Executor
这仨在我看来是玄学,参考Waker和Executor。把Task当作一个实现/封装了Future的魔法结构吧,futures和tokio中有各自的实现。Waker顾名思义,它的wake方法用于通知Executor当前Task已经完成,而Executor则会按照一定的调度策略来执行Task。
async & await
async和await这两个都是糖,async后面跟fn,或者闭包,或者花括号block,会把后面的内容转换成impl Future<Output=SomeType>。await则是会被转换成对future的执行动作。
Reactor
Tokio的一个基于mio实现的Waker的管理器,大概就是它会不断poll在pending的task,将就绪的task转移到Executor。
Stream & Sink
这俩的关系类同于CycleJS中的Source和Sink;Stream可以当作Iterator的异步版,一个让你一直读数据的来源,而Sink则是一个让你一直写数据的目标。
体验
让我感觉最神奇的是Future的花式转换:
use futures::task::{Context, Poll};
use futures::{executor, future};
fn read_line(_cx: &mut Context<'_>) -> Poll<i32> {
Poll::Ready(3)
}
fn main() {
let a = async { 0 };
let b = future::lazy(|_| 1);
let c = future::ready(2);
let d = future::poll_fn(read_line);
let tuple = future::join4(a, b, c, d);
let v = executor::block_on(tuple);
println!("{:?}", v);
}
在异步浅谈中有说,不要在异步函数中执行阻塞操作,所有阻塞操作都要替换成对应的async版本,否则task所在的线程就会被阻塞,然后那个线程上的所有task都得等待这个task了。
这里的同步应该强调的是:需要等待的系统调用。
假设使用tokio。thread::sleep使用tokio的time模块,Tcp/Ups/Uds以及io等都替换成tokio的net/io模块,sync中的channel/Mutex/RwLock也都替换成tokio的sync模块。如果用async-std,那也一样,凡是同步的都换成异步。
先到这儿,后面有了感想再继续加。