术语表
异步
在 Rust 的上下文中,异步代码指的是使用 async/await 语言特性的代码,它允许多个任务在少量线程(甚至单线程)上并发运行。
并发和并行
并发和并行是两个相关的概念,都用于谈论同时执行多个任务。如果某事并行发生,那么它也并发发生,但反之则不然:在两个任务之间交替,但从不同时处理两个任务是并发而不是并行。
Future (期物)
期物是一个值,它存储某些操作的当前状态。期物还有一个 poll
方法,使操作继续进行,直到它需要等待某些东西,例如网络连接。对 poll
方法的调用应该非常快速地返回。
期物通常通过在异步代码块中使用 .await
组合多个期物来创建。
Executor/scheduler (执行器/调度器)
执行器或调度器是重复调用 poll
方法来执行期物的东西。标准库中没有执行器,因此你需要一个外部库来实现此目的,而最广泛使用的执行器由 Tokio 运行时提供。
执行器能够在少量线程上并发运行大量期物。它通过在 awaits 处交换当前正在运行的任务来实现这一点。如果代码花费很长时间而没有到达 .await
,则称为“阻塞线程”或“不屈服于执行器”,这会阻止其他任务运行。
Runtime (运行时)
运行时是一个库,其中包含执行器以及与该执行器集成的各种实用程序,例如定时实用程序和 IO。运行时和执行器这两个词有时可以互换使用。标准库没有运行时,因此你需要一个外部库来实现此目的,而最广泛使用的运行时是 Tokio 运行时。
Runtime 这个词也在其他上下文中使用,例如,短语“Rust 没有运行时”有时用来表示 Rust 不执行垃圾回收或即时编译。
Task (任务)
任务是在 Tokio 运行时上运行的操作,由 tokio::spawn
或 Runtime::block_on
函数创建。通过组合期物(例如 .await
和 join!
)来创建期物的工具不会创建新任务,并且每个组合部分都被称为“在同一任务中”。
并行需要多个任务,但可以使用 join!
等工具在一个任务上并发执行多个操作。
衍生 (Spawning)
衍生 (Spawning) 是指使用 tokio::spawn
函数创建新任务。它也可以指使用 std::thread::spawn
创建新线程。
Async block (异步代码块)
异步代码块是创建运行某些代码的期物的简便方法。例如
let world = async {
println!(" world!");
};
let my_future = async {
print!("Hello ");
world.await;
};
上面的代码创建了一个名为 my_future
的期物,如果执行它,将打印 Hello world!
。它通过首先打印 hello,然后运行 world
期物来实现这一点。请注意,上面的代码本身不会打印任何内容——你必须实际执行 my_future
才能发生任何事情,方法是直接衍生它,或者在您衍生的内容中 .await
它。
Async function (异步函数)
与异步代码块类似,异步函数是创建函数的一种简便方法,其函数体变为期物。所有异步函数都可以重写为返回期物的普通函数
async fn do_stuff(i: i32) -> String {
// do stuff
format!("The integer is {}.", i)
}
use std::future::Future;
// the async function above is the same as this:
fn do_stuff(i: i32) -> impl Future<Output = String> {
async move {
// do stuff
format!("The integer is {}.", i)
}
}
这使用 impl Trait
语法来返回期物,因为 Future
是一个 trait。请注意,由于异步代码块创建的期物在执行之前不会执行任何操作,因此调用异步函数在它返回的期物被执行之前不会执行任何操作 (忽略它会触发警告)。
Yielding (让步)
在异步 Rust 的上下文中,让步是允许执行器在单个线程上执行多个期物的原因。每次期物让步时,执行器都能够将该期物与某些其他期物交换,并且通过重复交换当前任务,执行器可以并发执行大量任务。期物只能在 .await
处让步,因此在 .await
之间花费很长时间的期物可能会阻止其他任务运行。
具体来说,期物在每次从 poll
方法返回时都会让步。
Blocking (阻塞)
“阻塞”一词有两种不同的用法: “阻塞”的第一个含义只是等待某事完成,而阻塞的另一个含义是期物花费很长时间而不让步。为了消除歧义,你可以对第二个含义使用短语“阻塞线程”。
Tokio 的文档将始终使用“阻塞”的第二个含义。
要在 Tokio 中运行阻塞代码,请参阅 Tokio API 参考中的 CPU 密集型任务和阻塞代码 部分。
Stream (流)
Stream
是 Iterator
的异步版本,并提供值流。它通常与 while let
循环一起使用,如下所示
use tokio_stream::StreamExt; // for next()
while let Some(item) = stream.next().await {
// do something
}
Stream 这个词有时会令人困惑地用于指代 AsyncRead
和 AsyncWrite
traits。
Tokio 的流实用程序目前由 tokio-stream
crate 提供。一旦 Stream
trait 在 std 中稳定下来,流实用程序将被移动到 tokio
crate 中。
Channel (通道)
通道是一种工具,允许代码的一部分向其他部分发送消息。Tokio 提供了许多通道,每个通道都有不同的用途。
- mpsc:多生产者、单消费者通道。可以发送多个值。
- oneshot:单生产者、单消费者通道。可以发送单个值。
- broadcast:多生产者、多消费者。可以发送多个值。每个接收者都会看到每个值。
- watch:单生产者、多消费者。可以发送多个值,但不保留历史记录。接收者只能看到最近的值。
如果你需要一个多生产者、多消费者通道,其中每个消息只有一个消费者看到,你可以使用 async-channel
crate。
还有一些通道用于异步 Rust 之外,例如 std::sync::mpsc
和 crossbeam::channel
。这些通道通过阻塞线程来等待消息,这在异步代码中是不允许的。
Backpressure (背压)
背压是一种用于设计能够良好响应高负载的应用程序的模式。例如,mpsc
通道有有界和无界两种形式。通过使用有界通道,如果接收者无法跟上消息的数量,接收者可以对发送者施加“背压”,这避免了随着通道上发送越来越多的消息而导致内存使用量无限制地增长。
Actor (Actor 模型)
用于设计应用程序的设计模式。Actor 模型指的是独立衍生的任务,它代表应用程序的其他部分管理某些资源,并使用通道与应用程序的这些其他部分进行通信。
有关 Actor 模型的示例,请参阅通道章节。