本文以tokio为例简单介绍Rust异步编程相关的一些知识。
首先让我们看看为什么使用rust来进行异步编程。这里tokio官方给出了一个性能测试的对比,可以看到tokio是性能最好,实际上运行这个基准测试的时候,tokio性能更好的2.0版本尚未发布,否则估计性能还有很大提升。因此,我们可以认为需要非常极致性能的时候,我们可以选择rust+tokio来实现。
![f10545abc8ddbd3368bd14da163ff3b6.png](https://i-blog.csdnimg.cn/blog_migrate/02d7fc08f1557bd309fb0fcc6240f0ec.jpeg)
Rust网络编程
Rust实际上并不跟一定的网络编程模型强绑定,实际rust可以实现阻塞IO+多线程,非阻塞IO+回调,用户态线程等多种模型。这里着重介绍Rust实现的用户态线程。
首先,Rust的用户态线程是一种基于Future的用户态线程,关于Future本身,本文后续部分有详细论述。
其次,由于是Rust实现,因此可以做到零成本抽象,并且更容易做到安全。
最后,由于没有运行时大量内存分配,没有动态逻辑分派,也没有GC开销,所以该实现的效率非常高。
Rust异步编程是构建在操作系统相关API上,MIO库类似Java的Nio库,针对多种操作系统的不同API做了统一封装。Future库类似Java的Future库,提供了相关接口和常用的组合能力。Tokio构建于两者之上,在MIO和future的基础上实现了用户态线程。使用Tokio进行异步编程的技术栈如下,需要注意的是,应用程序会同时接触到Tokio和future的API。
![6eafbb953a254c9f10ef92fd4a0ccee8.png](https://i-blog.csdnimg.cn/blog_migrate/22de7afed8f1a1c6085a7f5e37ea3a05.jpeg)
Futures
future是rust异步编程的核心。首先我们介绍什么是future。future是一段异步计算程序,可以在将来获取产生的数据。举例来说,获取数据库查询结果,RPC调用这些实际上都可以使用future来实现。通常实现future有两种模式,一种基于推模式,也被称为基于完成的模式,一种基于拉模式,也被称为基于就绪的模式。Rust的future库实现了基于拉模式的future。
rust的future选择拉模式来实现。接口定义如下:
pub trait Future {
type Item;
type Error;
fn poll(&mut self) -> Poll<:item self::error>;
}
假设一个future要做这样的功能,从TCP数据流读取数据并计算自己读了多少个字节并进行回调。那用代码表示:
struct MyTcpStream {
socket: TcpStream,
nread: u64,
}
impl Future for MyTcpStream {
type Item =u64;
type Error = io::Error;
fn poll(&mut self) -> Poll {
let mut buf = [0;10];
loop {
match self.socket.read(&mut buf) {
Async::Ready(0) => return Async::Ready(self.nread),
Async::Ready(n) => self.nread += n,
Async::NotReady => return Async::NotReady,
}
}
}
}
每次调用poll方法,MyTcpStream都会调用socket的read方法(这里的TcpStream本身也是一个future,read内部也是调用poll方法),当read返回为Async::NotReady的时候,调度器会将当前的Task休眠,如果返回Async::Read(n)表示读到了数据,则给计数器加对应的数,如果返回Async::Ready(0),则表示TcpStream里有的数据已经读完,就将计数器返回。
为了方便大家使用,future库包提供了很多组合子,以AndThen组合子为例:
enum AndThen {
First(A, F),
}
fn poll(&mut self) -> Async {
match fut_a.poll {
Async::Ready(v) => Async::Ready(f(v)),
Async::NotReady => Async::NotReady,
}
}
这里AndThen枚举,First有两个值,其中A是一个future,F是一个闭包,AndThen实现的poll方法,就是假如调用future_a的poll方法有返回值,那么就调用闭包,并将其返回值包装为Async::Ready返回,如果poll的返回值是Async::NotReady则同样返回Async::NotReady。有了这个AndThen方法,通过组合子函数(比如and_then实际上是将上一个future和闭包传入生成一个AndThen future),我们就可以实现一些复杂逻辑:
let f=MyTcpStream::connect(&remote_addr)
.and_then(|num| {println!("already read %d