Rust:什么是异步运行时?有哪些可用?
与其他 Rust 功能不同,你不能只编写await
代码并运行它。你需要使用像 Tokio 或 async-std 这样的一个异步运行时。但这是为什么?这些运行时有什么作用?
我开始致力于可移植性和互操作性计划,这是异步基础工作组改进 Rust 异步编程工作的一部分。这是这个工作的背景博客文章。我希望很快能有更多关于这项工作的挑战和目标的博文。
01 对 future 快速浏览
future 代表异步计算。换句话说,它是一个可能仍在计算过程中的值。在 Rust 中,future 是一个实现 Future[1] trait 的对象,它只需要实现一个 poll
方法。要使用 future,只需重复调用 poll
方法,直到它返回有用的东西。
异步函数只是编写返回 future 函数的一种便捷方式。所需要的只是调用poll
的某些东西。
02 执行者(Executors)
到此为止,就这么简单。但事实证明,如何调用poll
函数是一个重要而困难的问题。我们不想太频繁地调用它,因为这会浪费 CPU 周期并可能使其他任务(我们很快就会知道一个任务到底是什么)无法取得进展。我们不想太频繁地调用它,因为这意味着我们的 future 不会像它应该的那样迅速取得进展。
此外,没有最好的方法来解决这个问题。不同的约束和环境使不同的方法或多或少是最佳的。
Executors 通常提供两个关键的 API:block_on
和 spawn
。block_on
在当前线程上运行一个 future,阻塞任何其他活动,并在 future 完成时返回。spawn
在不阻塞当前线程的情况下运行 future,立即返回。复杂的执行程序可能支持这些功能的变化。
03 输入输出
前面讲 future 的时候,我说 future 代表的是一个被计算过程的价值。但是 future 的真正动机通常不是消耗 CPU 而是等待 IO 完成。当你有很多并发任务需要等待 IO 时,Futures(以及一般的异步编程)是最有效的。
操作系统为这种异步 IO 提供了功能,但它是非常低级的(例如 epoll API)。一个好的执行器必须与操作系统交互,以便它可以在最佳时间唤醒 Future(有时称为反应器)。
另外,要实际使用异步 IO,我们需要一堆抽象 — 用于抽象不同类型 IO 的特征(例如,std 中的Read
和Write
特征的异步等价物)、对文件、套接字、信号和其他 IO 接口的抽象以及实用程序对于诸如缓冲或路径操作之类的事情。
04 什么是运行时?
运行时从执行器开始,用于运行 future,包括与操作系统交互以驱动 IO 操作。它通常包括用于异步 IO 的特征、类型和函数库。通常,运行时还会向高级用户公开其一些构建块,例如Task
从 async-std 的抽象,以及实用程序,例如用于固定或编写异步main
函数的辅助工具。最后,运行时通常包括异步编程的其他基础(例如通道和锁,或定时器功能),这些基础在某种程度上与执行器相关。
换句话说,异步运行时的基本部件是执行器,但异步运行时还包括使编写异步代码变得实用(不仅仅是可能)的库。这样做时,异步运行时就像异步编程的标准库和异步生态系统的关键部分。
05 对运行时的简短且不完整的调查
我将简要描述几个主要异步运行时的定义特征 (IMO)。这不是深度比较,我不会尝试选择最佳运行时(这取决于你的程序的特定约束)。
还有其他可用的运行时(大概是一些通常不可用的闭源运行时)。其中一些并不广为人知或较少使用(或至少我不知道),其他人专门用于某个场景(例如 Fuschia 的运行时),主要用于教育而不是用于生产用途(例如,Whorl),或是其他运行时的改写(例如,AIUI Actix 在内部使用 Tokio,但根据自己的需要对其进行改写)。
Futures
futures[2] crate 是 Rust 官方 crate。它开始是一个开发Future
trait 和相关库的地方,它们包含在 Rust 的标准库中。现在主要处于维护状态。它包括一个基本的执行器和一堆有用的流、IO 等特性。然而,它省略了异步 IO 的低级功能,所以它不是一个完整的解决方案。因此,它因其特性和流支持等而被广泛使用,但通常与来自另一个运行时的执行器(和具体的 IO 类型)结合使用。
Tokio
Tokio[3] 是最古老且使用最广泛的运行时之一,并且可能是生产中使用最多的运行时。它具有高性能、可定制且灵活的执行器。值得注意的是,future 不必传递到后台线程来执行,这对性能很有好处,但需要相当严格的关于“在上下文中”执行器的规则。
Tokio 的 IO 特性与生态系统中的大多数其他特性和标准库中的同步版本略有不同。具体来说,数据被读入抽象缓冲区类型,而不是 &mut [u8]
。这使它们更灵活,有时性能更高,但使用起来也有点棘手。
Smol 和 async-std
Async-std[4] 和 Smol[5] 是两个基于 smol-rs 组件的运行时。Async-std 被设计得尽可能接近同步标准库,而 Smol 被设计得更小。它们被广泛使用并且可以投入生产。与 Tokio 相比,Smol 执行器总是使用后台线程来执行 future,这牺牲了一些潜在的性能以获得更好的可用性。Async-std/Smol 包括一些有趣的运行时构建块,例如Task
抽象 - Smol 的目标之一是为运行时提供构建块,而不仅仅是开箱即用的体验。
async-std/Smol 的 IO 特性和实用程序大多继承自 futures crate。
Glommio
Glommio[6] 是一个基于 thread-per-core 哲学并使用 io_uring 实现的专用运行时。它主要是为磁盘 IO 设计的(参见网络 IO,它一直是异步编程的传统方式)。与 Tokio 和 async-std 不同,Glommio 不是通用的异步运行时,也不包含诸如AsyncRead
trait 之类的东西。但对于它的应用场景,它是一个完整的解决方案。
Embassy
Embassy[7] 是专为嵌入式开发设计的运行时。特别是,它避免了分配并且不需要堆。
Bastion
Bastion[8] 是一个 actor 运行时,因此比通用运行时更高级。它被设计为具有容错性和高可用性。它没有用于一般 IO 的库(我假设你会在实践中将它与 futures crate 一起使用)。
原文链接:https://ncameron.org/blog/what-is-an-async-runtime/
参考资料
Future: https://doc.rust-lang.org/nightly/std/future/trait.Future.html
[2]futures: https://github.com/rust-lang/futures-rs
[3]Tokio: https://tokio.rs/
[4]Async-std: https://async.rs/
[5]Smol: https://github.com/smol-rs/smol
[6]Glommio: https://github.com/DataDog/glommio
[7]Embassy: https://github.com/embassy-rs/embassy
[8]Bastion: https://bastion.rs/
我是 polarisxu,北大硕士毕业,曾在 360 等知名互联网公司工作,10多年技术研发与架构经验!2012 年接触 Go 语言并创建了 Go 语言中文网!著有《Go语言编程之旅》、开源图书《Go语言标准库》等。
坚持输出技术(包括 Go、Rust 等技术)、职场心得和创业感悟!欢迎关注「polarisxu」一起成长!也欢迎加我微信好友交流:gopherstudio