06-Rust中的异步抽象
12.3.1 Rust中的异步抽象
异步网络I/O有很多优点,但是以原始形式对它们进行编程是很困难的。幸运的是,Rust为我们提供了第三方软件包形式的便捷抽象,用于处理异步I/O。当处理非阻塞套接字和底层套接字轮询机制时,它为开发人员简化了大多数复杂状态机的处理。可供用户选择的两个底层抽象软件包是futures和mio。让我们对它们进行简要介绍。
mio
使用非阻塞套接字时,我们需要一种方法来检索套接字是否已准备好执行所需的操作。当我们有数千或更多套接字要管理时,情况可能会更糟。我们可以采用非常低效的方式循环检查套接字状态,并在准备好以后执行操作。但是有更好的方法可以做到这一点。在UNIX中,我们可以进行poll系统调用,你可以为其提供监控目标事件的文件描述符列表。然后它被select系统调用取代,这让情况得到了一些改善。但是,select和poll都不具有可扩展性,因为它们基本上是针对底层的循环访问,并且随着越来越多的套接字添加到其监视列表中,迭代时间呈线性增长。
在Linux下出现了epoll,它是当前最高效的文件描述符多路复用API。大多数想要进行异步I/O的网络和应用程序都会采用它。其他平台也有类似的抽象,例如macOS和BSD中的kqueue。在Windows下,我们有IO完成端口(IO Completion Port,IOCP)。
mio为我们提供了这些底层机制的高度抽象,它可以为上述所有I/O复用API提供跨平台、高效的接口。mio是一款底层软件包,它提供了一种为socket事件设置反应器的便捷方法。它的网络原语和标准库类型相似,例如TcpStream类型,不过默认情况下它是非阻塞的。
futures
mio 杂耍式的套接字轮询状态机用起来并不是很方便。为了提供可供应用程序开发人员使用的高级API,我们有futures软件包。它提供了一个名为Future的特征,这是该软件包的核心组成部分。future表示一种不能立即有效的算法思路,但可能在后续生效。让我们来看看Future特征的类型签名,以了解更多的相关信息:
pub trait Future {
type Item;
type Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error>;
}
Future是一种关联的类型特征,它定义了两种类型:Item类型表示Future将解析的值,Error类型表示操作失败时的错误类型。它们与标准库中的Result类型非常相似,但是没有立即得到结果的原因是不会马上执行相关计算。
Future值自身不能用于构建异步应用程序,你需要将某种反应器和事件循环来推进future完成。根据设计,让它们成功获取值或操作失败并出现错误的唯一方法是轮询它们,此操作由被称为poll的单个请求方法表示。poll函数指定了应该如何完成future过程。future也可以由几件事情组合而成,从而一个接一个地链接起来。为了推进future,我们需要一个反应器和事件循环实现,这是由tokio软件包提供的。
tokio
我们的tokio软件包整合了上述两种抽象,以及工作窃取调度程序、事件循环和计时器实现,它提供了一个运行时来驱动future完成。通过tokio框架,你可以生成多个future并让它们同时运行。
tokio的诞生为构建协议无关的、强大的、高性能的异步网络应用程序提供了一种解决方案,同时提供了所有网络应用程序中常见的通用模式抽象。tokio软件包在技术上是一种运行时,由一个线程池、事件循环,基于mio的I/O事件的反应器组成。对于“运行时”,我们的意思是使用tokio开发的Web应用程序都会将上述组件作为应用程序的一部分运行。
tokio框架中的future是在一个任务中运行的。一个任务类似于用户空间线程或绿色线程,执行程序负责调度执行任务。
当future没有任何数据要解析,或者在TcpStream客户端读取正在等待到达套接字的数据时,它将返回NoReady状态。但是在执行此操作时,还需要向反应器注册感兴趣的内容,以便能够再次获知服务器上的任何新数据。
当创建future时,无须执行任何其他操作。对于future定义的工作任务,必须提交给执行程序完成。在tokio中,任务是可以执行future的用户级线程。在poll方法的实现中,任务必须安排自己稍后执行轮询,以防相关工作停滞。为此,它必须将其任务处理程序传递给反应器线程。在Linux中,反应器是mio软件包。