一、前言
异步是一个非常强大特性,它有相当多的实现模型,其中比较知名的“协程”,go语言的并发编程就是借助的“协程”这一概念达到了非常高的性能,
不理解协程的可以查看另外一篇文章:协程与线程。
在这众多实现模型中,最基本就是多线程模型了,由于其是系统本身底层提供的,所以写起来、理解起来都会较为简单。
还有就是事件驱动模型,和windows消息机制差不多,不理解的可以参考:windows编程入门。
除了上面三个之外,另一个常用的异步模型便是async/await
。
它的主要麻烦之处在于其底层实现非常复杂,但好在这并不需要我们自己去实现。直接使用即可。
第二个麻烦之处在于其不好理解,这便是本章要做的事情了。
由于rust语言本身提供了await
、async
以及Future
这三个async/await
模型最基础的特性,所以使得实现该模型变得更加容易。
rust中异步库有很多,但目前性能最好、效率最高、最流行的库是一个叫tokio
的。
从一张网上流传甚广、据说是官方测试各种异步框架效率也能看出tokio
的恐怖之处:
该图展示的大概是各种异步框架所实现的最小http
服务器单位时间所能处理的请求数量。
所以本章也将直接以tokio为例对rust中异步的使用进行介绍。
二、基本理解与使用
首先你需要下载tokio
,在配置文件中添加下面这行代码:
tokio = { version = "1.36.0", features = ["full"] }
它的基本使用方法如下:
fn main() {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
println!("hello tokio");
})
}
看起来似乎很复杂,这里对其简单解析一下:
- 首先我们需要创建一个
tokio
的“运行时”,也就是上面的rt
变量,它将成为整个程序的异步基础。 - 然后使用其上的
block_on
函数就可以去调用异步代码块,比如这里的async{}
就是一个异步运行的代码块。 - 此时
main
函数会始终阻塞在block_on
函数内,直到传入的函数、代码块被全部执行结束,才会结束调用。
你可能会觉得这看上去也没什么呀?似乎就是等一段代码结束而已。
但事实上tokio
创建的这个运行时内部在这一过程中是做了相当多事情的,这里简单说一下它可能的工作原理:
- “运行时”创建的同时,其内部会管理一个类似“线程池”一样的东西,被称为“执行器”,不理解的可以参考文章:手写一个线程池。
- “运行时”内部同时还会有一个类似“事件循环”的东西,叫做“轮询”,可以将其类比于windows系统的窗口消息机制,但并不完全相同。
- 你传入的所有异步任务、都将由“轮询”接受,并由“运行时”自动分配执行器去执行该任务。
虽然可能其实际的工作过程远比这个复杂的多,但其基本原理大概如此。
所以我们现在就能够通过异步写出下面这样的代码:
可以看到,虽然我现在是先调用的hello
函数,但先输出的却是后调用的world
。
异步函数的写法相当简单,就像上图的hello
一样,你只需要在函数前面添加一个async
关键字即可。
但此时就要注意了,任何异步函数都只能在异步函数、异步代码块中进行调用才有效果。
因为你调用异步函数会立即得到一个返回值,也就是上图中其编译器自动推导出来的实现了Future
特性的ret
变量。
你只有调用其上的await
关键字之后,该函数才会被触发执行。
实际上await
关键字的逻辑是将该任务提交给“轮询”,让其为本任务分配一个“执行器”去执行,如果你不调用await
,那么轮询就不知道需要执行该任务,该任务就无法被执行。