本文对应相关书籍17.1章节。
先言:async和多线程没有什么密切关系,当然也不是毫无关系!
一、async在rust到底是什么
async中文的意思是异步,在编程中指的是:执行一段代码,主线程可以在等待完成之前,先做其它事情。
如果有写过前端代码,就明白这个东西。例如经典的jquery的ajax默认就是异步执行的。
原书花费了一段文字先解释:并行和并发
* 1.并行(parallel)和并发(concurrency)都有一段时间内共同执行多个任务的意思
* 2.并行指的是在同一时刻,有多个任务在执行。并发是指在某一时间段内,有多个任务在交替执行
* 3.并发某种程度上理解为资源不够,分时处理;并行理解为资源充足,同时处理。
* 4.当使用 Rust 中的 async 时,我们总是在处理并发
* 5.取决于硬件、操作系统和所使用的异步运行时(async runtime)-- 稍后会介绍更多的异步运行时!
* 并发也可能在底层使用了并行 -- 这个需要特别注意。
说了这么一大段就是为了说明一个事情:使用 Rust 中的 async 时,我们总是在处理并发
也就是说,一般情况下,在rust中使用异步,基本就是意味着是cpu分时处理,但具体怎么实现和代码以及系统系统有关!!!
二、async、await和future
await-等待
future-未来,rust中用于表示一个数据类型:现在不会有,但是过了一段时间会有
这些都是很熟悉的字眼,在java,js中是常常出现的。
* 1.future 是一个现在可能还没有准备好但将在未来某个时刻准备好的值
* 2.Rust 提供了 Future trait 作为基础组件,这样不同的异步操作就可以在不同的数据结构上实现
* 3.每一个实现了 Future 的类型会维护自己的进度状态信息和 “ready” 的定义
* 4.async 关键字可以用于代码块和函数
* 5.在一个 async 块或 async 函数中,可以使用 await 关键字来等待一个 future 准备就绪,这一过程称为 等待一个 future
* 6.检查一个 future 并查看其值是否已经准备就绪的过程被称为 轮询(polling)
* 7.在大多数情况下,编写异步 Rust 代码时,我们使用 async 和 await 关键字。
* Rust 将其编译为等同于使用 Future trait 的代码,这非常类似于将 for 循环编译为等同于使用 Iterator trait 的代码
三、示例
use trpl::{Either, Html}; use std::thread;fn main() {let args: Vec<String> = std::env::args().collect();println!("{:?}", args);//注意async关键字,并不会导致开启一个新的线程,仅仅意味着可以在内部使用await关键字等待异步操作完成。trpl::run(async {//lazy future 这里不会执行let title_fut_1 = page_title(&args[1]);//lazy future 这里不会执行let title_fut_2 = page_title(&args[2]);//race中的代码使用future::select执行并发,并返回最先完成的future。//因为这里真正开始执行,所以才会在多次运行的时候,返回可能不同的结果//race方法返回的是一个Either类型,Either是一个枚举类型,有两个值,Left和Right。let (url, maybe_title) = match trpl::race(title_fut_1, title_fut_2).await {Either::Left(left) => left,Either::Right(right) => right,};println!("{url} returned first");match maybe_title {Some(title) => println!("Its page title is: '{title}'"),None => println!("Its title could not be parsed."),}}) } /*** 带了async的函数会返回一个future,这个future的类型是impl Future<Output = ()>,* output是一个泛型参数,这里是(),表示这个future的返回值是一个元组,*/ async fn page_title(url: &str) -> (&str, Option<String>) {//这个语句会证实函数page_title是运行在主线程中...,并不是多线程的.println!("{}",url);println!("线程{:?}正在执行", thread::current().id());let text = trpl::get(url).await.text().await;let title = Html::parse(&text).select_first("title").map(|title| title.inner_html());(url, title) }
注意:第一次执行,你会怀疑是不是卡死了,这个不是问题,是rusts-script需要耗费时间处理trpl(后者又依赖很多乱起八糟的),所以耗时颇长.
rust-script -d trpl 17.2_future.rs https://www.cnblogs.com/lzfhope/p/18459068 https://www.cnblogs.com/lzfhope/p/18664452
这个命令执行了几次:
可以看到,结果会有所不同,这意味着 两个page_title在执行的时候不是固定,不是串行的!
这个trpl是rust提供的示例代码,下面是感兴趣的部分:
pub fn run<F: Future>(future: F) -> F::Output {let rt = Runtime::new().unwrap();rt.block_on(future) }pub async fn race<A, B, F1, F2>(f1: F1, f2: F2) -> Either<A, B> whereF1: Future<Output = A>,F2: Future<Output = B>, {let f1 = pin!(f1);let f2 = pin!(f2);match future::select(f1, f2).await {Either::Left((a, _f2)) => Either::Left(a),Either::Right((b, _f1)) => Either::Right(b),} }
这个Runtime是tokio单元包的成员,表示运行时(需要注意的是,rust反复强调运行时可以自建)
future::select 这个函数用于并发执行多个future,并返回一个future值。
四、简单执行async+await是什么样的?
前面的例子,有启用并发。 这也是常规的做法。
但是我也感兴趣,如果不用并发又是什么样的?
先说结论:就是什么也没有发生
use std::thread;struct Hero {name: String,age: u32, }fn main() {let hero = Hero {name: "孙悟空".to_string(),age: 100,};let ws = Hero {name: "武松".to_string(),age: 99,};let str1 = run(&hero);let str2 = run(&ws); }async fn fight(h: &Hero) -> String {let temp = h.name.clone() + "正在战斗中....";println!("{}", temp);temp.to_string() } //这个方法要返回实现Debug特质的类型 async fn run(h: &Hero) -> String {let r = fight(&h).await;println!("{}", r);r }
五、小结
顺便也看了一些其它的资料。
- 创建异步函数的方式:函数(代码块)前加async,在函数体内添加await。 例外情况:不能把main标记为async
- 异步函数可以采用rust现有的两个异步运行时来运行:tokio或者async-std。似乎没有更多选择,此外为什么又那么多运行时,难道这个不应该是rust核心库的功能吗
- 异步的底层实现可能不一定是并发,也可能是并行,要看情况而定
- async函数默认返回的是 Future<Output = ()>。奇怪的语法又出现了,rust不要求你把返回结果显示地定义为Future。看第一个实例的page_title函数
- 希望rust少一些这种默认的东西
- 少一些故意和其它语言不同的东西
- 此外,我也好奇,如果特意把返回类型写成Future会怎么样?
- 看了这些内容,距离异步编程还差99%