|
|
|
@ -1 +1,57 @@
|
|
|
|
|
# Async 和 await
|
|
|
|
|
|
|
|
|
|
> [ch17-00-async-await.md](https://github.com/rust-lang/book/blob/main/src/ch17-00-async-await.md)
|
|
|
|
|
> <br>
|
|
|
|
|
> commit 3111eda07a4a4692bf69e3aaad999d840ac9c138
|
|
|
|
|
|
|
|
|
|
很多我们要求计算机处理的操作都需要一定的时间才能完成。例如,如果你使用视频编辑器来创建一个家庭聚会的视频,视频导出可能会花费从几分钟到几个小时的时间。类似地,下载由你家庭成员分享的视频也可能花费很长时间。如果能在等待这些长时间运行的操作完成之前做点其他事就太好了。
|
|
|
|
|
|
|
|
|
|
视频导出会尽可能使用所有的 CPU 和 GPU。如果你只有一个 CPU 核,同时操作系统在导出完成前也不会暂停,那么在其运行期间你无法使用计算机进行任何其他操作。但是这将是非常糟糕的体验。相反计算机的操作系统可以(也确实可以)隐式地中断导出足够长的时间来让你同时完成其他操作。
|
|
|
|
|
|
|
|
|
|
下载文件则有所不同。它不占用非常多的 CPU 时间。相反 CPU 需要等待来自于网络的数据。虽然可以在部分数据就绪时就开始读取,等待余下的数据也可能花费很长时间。即便数据全部就绪了,视频文件也可能非常大,所以完全加载也可能花费一些时间。虽然这可能就是一两秒,不过这对于一个现代处理器来说也是非常长的时间。如果能让 CPU 在等待网络调用完成的同时去处理别的工作就好了。所以同上操作系统会隐式地中断你的程序以便其它工作可以在网络操作进行的同时继续。
|
|
|
|
|
|
|
|
|
|
> 注意:视频导出这类操作通常被称为 “CPU 密集型”(“CPU-bound”)或者 “计算密集型”(“compute-bound”)操作。其受限于计算机 *CPU* 或 *GPU* 处理数据的能力,和其所能利用的速度。下载视频这类操作通常被称为 “IO 密集型”(“IO-bound”)操作,因为其受限于计算机 “输入输出” 的速度。它最多只能与通过网络收发数据的速度一样快。
|
|
|
|
|
|
|
|
|
|
在上述两个例子中,操作系统的隐式中断提供了一种形式的并发。不过这种并发只发生在整个程序的级别:操作系统中断一个程序并让其它程序继续处理。在很多场景中,因为我们能比操作系统在更为细的粒度上理解我们的程序,所以我们可以观察到很多操作系统无法察觉的并发机会。
|
|
|
|
|
|
|
|
|
|
例如,如果我们在构建一个管理文件下载的工具,我们应该以一种开始一个下载任务时不会锁定 UI 的方式来编写程序,并且用户应该能够同时开始多个下载。不过很多操作系统与网络交互的 API 都是 *阻塞* 的 (*blocking*)。也就是说这些 API 在数据完全就绪之前阻塞程序的运行。
|
|
|
|
|
|
|
|
|
|
> 注意:如果你仔细思索一下,会发现这是 *大部分* 函数调用的工作方式!不过我们通常将 “阻塞” 这个术语保留给那些与文件、网络或其它计算机资源交互的函数调用,因为这些是单个程序可以从 *非* 阻塞操作中获益的地方。
|
|
|
|
|
|
|
|
|
|
我们可以新建专用的线程来下载每个文件以免阻塞主线程。然而,最终我们会发现这些线程的开销会成为一个问题。如果这些调用在最开始就是非阻塞的就更好了。最后,如果能使用阻塞代码中那样直接的风格编写非阻塞代码就更好了。比如这样:
|
|
|
|
|
|
|
|
|
|
```rust,ignore,does_not_compile
|
|
|
|
|
let data = fetch_data_from(url).await;
|
|
|
|
|
println!("{data}");
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
这正是 Rust async 抽象所提供的。不过在讲解它们在实践中如何工作之前,让我们稍微绕个远路来了解一下并行(parallelism)和并发(concurrency)的区别。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 并行与并发
|
|
|
|
|
|
|
|
|
|
在上一章中,我们将并发和并行大体上视为可相互替换的。现在我们需要更精确的区分它们,因为在我们开始讲解后它们的区别就会显现出来。
|
|
|
|
|
|
|
|
|
|
思考一下不同的团队分割方法来开发一个软件项目。我们可以分配给一个个人多个任务,或者可以每个团队成员一个任务,或者可以采用这两种方法的组合。
|
|
|
|
|
|
|
|
|
|
当一个个人在任何一个任务完成前同时处理多个任务,这就是 *并发*。可能你在计算机上开着两个不同的项目,当你对一个项目感到厌烦或者遇到困难时,可以切换到另一个项目。因为你是单独一个人,所以无法真正同时进行两个任务,但是你可以多任务处理,通过切换来处理多个任务。
|
|
|
|
|
|
|
|
|
|
<figure>
|
|
|
|
|
|
|
|
|
|
<img alt="并发工作流" src="img/trpl17-01.svg" class="center" />
|
|
|
|
|
|
|
|
|
|
<figcaption>图 17-1:一个并发工作流,在任务 A 和任务 B 之间切换</figcaption>
|
|
|
|
|
|
|
|
|
|
</figure>
|
|
|
|
|
|
|
|
|
|
当你同意将一组任务在组员中分配,每一个组员分配一个任务并单独处理它,这就是 *并行*。每个组员可以真正同时进行工作。
|
|
|
|
|
|
|
|
|
|
<figure>
|
|
|
|
|
|
|
|
|
|
<img alt="并行工作流" src="img/trpl17-02.svg" class="center" />
|
|
|
|
|
|
|
|
|
|
<figcaption>图 17-2:一个并行流,其中任务 A 和任务 B 的工作同时独立进行</figcaption>
|
|
|
|
|
|
|
|
|
|
</figure>
|
|
|
|
|
|
|
|
|
|
在这两种场景中,你可能需要协调不同的任务。
|
|
|
|
|