Fix typo in tokio/select.md

pull/376/head
lijinpeng 3 years ago
parent 924226b860
commit 9d94d4c335

@ -54,7 +54,7 @@ async fn main() {
let (tx2, rx2) = oneshot::channel(); let (tx2, rx2) = oneshot::channel();
tokio::spawn(async { tokio::spawn(async {
// 等待 `some_peration` 的完成 // 等待 `some_operation` 的完成
// 或者处理 `oneshot` 的关闭通知 // 或者处理 `oneshot` 的关闭通知
tokio::select! { tokio::select! {
val = some_operation() => { val = some_operation() => {
@ -62,12 +62,10 @@ async fn main() {
} }
_ = tx1.closed() => { _ = tx1.closed() => {
// 收到了发送端发来的关闭信号 // 收到了发送端发来的关闭信号
// // `select` 即将结束,此时,正在进行的 `some_operation()` 任务会被取消,任务自动完成,
// `select` 即将结束,此时,正在进行的 `some_operation()` 任务会被取消,任务自动完成, // tx1 被释放 // tx1 被释放
} }
} }
}); });
tokio::spawn(async { tokio::spawn(async {
@ -85,10 +83,10 @@ async fn main() {
} }
``` ```
上面代码的重点就在于 `tx1.closed` 所在的分支,一旦发送端被关闭,那该分支就会被执行,然后 `select` 会退出,并清理掉还没执行的第一个分支 `val = some_operation()` ,这其中 `some_peration` 返回的 `Future` 也会被清理,根据之前的内容,`Future` 被清理那相应的任务会立即取消,因此 `some_oeration` 会被取消,不再执行。 上面代码的重点就在于 `tx1.closed` 所在的分支,一旦发送端被关闭,那该分支就会被执行,然后 `select` 会退出,并清理掉还没执行的第一个分支 `val = some_operation()` ,这其中 `some_operation` 返回的 `Future` 也会被清理,根据之前的内容,`Future` 被清理那相应的任务会立即取消,因此 `some_operation` 会被取消,不再执行。
#### Future 的实现 #### Future 的实现
为了更好的理解 `select` 的工作原理,我们来看看如果使用 `Future`。当然,这里是一个简化版本,在实际中,`select!` 会包含一些额外的功能,例如一开始会随机选择一个分支进行 `poll` 为了更好的理解 `select` 的工作原理,我们来看看如果使用 `Future` 该如何实现。当然,这里是一个简化版本,在实际中,`select!` 会包含一些额外的功能,例如一开始会随机选择一个分支进行 `poll`
```rust ```rust
use tokio::sync::oneshot; use tokio::sync::oneshot;
@ -150,7 +148,7 @@ async fn main() {
`select` 宏开始执行后,所有的分支会开始并发的执行。当任何一个**表达式**完成时,会将结果跟**模式**进行匹配。若匹配成功,则剩下的表达式会被释放。 `select` 宏开始执行后,所有的分支会开始并发的执行。当任何一个**表达式**完成时,会将结果跟**模式**进行匹配。若匹配成功,则剩下的表达式会被释放。
最常用的**模式**就是用变量名去匹配表达式返回的值,然该变量就可以在**结果处理**环节使用。 最常用的**模式**就是用变量名去匹配表达式返回的值,然该变量就可以在**结果处理**环节使用。
如果当前的模式不能匹配,剩余的 `async` 表达式将继续并发的执行,直到下一个完成。 如果当前的模式不能匹配,剩余的 `async` 表达式将继续并发的执行,直到下一个完成。
@ -245,7 +243,7 @@ async fn main() {
## 错误传播 ## 错误传播
在 Rust 中使用 `?` 可以对错误进行传播,但是在 `select!` 中,`?` 如何工作取决于它是在分支中的 `async` 表达式使用还是在结果处理的代码中使用: 在 Rust 中使用 `?` 可以对错误进行传播,但是在 `select!` 中,`?` 如何工作取决于它是在分支中的 `async` 表达式使用还是在结果处理的代码中使用:
- 在分支中 `ascyn` 表达式使用会将该表达式的结果变成一个 `Result` - 在分支中 `async` 表达式使用会将该表达式的结果变成一个 `Result`
- 在结果处理中使用,会将错误直接传播到 `select!` 之外 - 在结果处理中使用,会将错误直接传播到 `select!` 之外
```rust ```rust
@ -321,7 +319,7 @@ async fn main() {
上面代码中,`rx` 通道关闭后,`recv()` 方法会返回一个 `None`,可以看到没有任何模式能够匹配这个 `None`,那为何不会报错?秘密就在于 `else` 上:当使用模式去匹配分支时,若之前的所有分支都无法被匹配,那 `else` 分支将被执行。 上面代码中,`rx` 通道关闭后,`recv()` 方法会返回一个 `None`,可以看到没有任何模式能够匹配这个 `None`,那为何不会报错?秘密就在于 `else` 上:当使用模式去匹配分支时,若之前的所有分支都无法被匹配,那 `else` 分支将被执行。
## 借用 ## 借用
当在 Tokio 中生成( spawan )任务时,其 async 语句块必须拥有其中数据的所有权。而 `select!` 并没有这个限制,它的每个分支表达式可以直接借用数据,然后进行并发操作。只要遵循 Rust 的借用规则,多个分支表达式可以不可变的借用同一个数据,或者在一个表达式可变的借用某个数据。 当在 Tokio 中生成( spawn )任务时,其 async 语句块必须拥有其中数据的所有权。而 `select!` 并没有这个限制,它的每个分支表达式可以直接借用数据,然后进行并发操作。只要遵循 Rust 的借用规则,多个分支表达式可以不可变的借用同一个数据,或者在一个表达式可变的借用某个数据。
来看个例子,在这里我们同时向两个 TCP 目标发送同样的数据: 来看个例子,在这里我们同时向两个 TCP 目标发送同样的数据:
```rust ```rust
@ -357,7 +355,7 @@ async fn race(
如果你把连接过程放在了结果处理中,那连接失败会直接从 `race` 函数中返回,而不是继续执行另一个分支中的连接! 如果你把连接过程放在了结果处理中,那连接失败会直接从 `race` 函数中返回,而不是继续执行另一个分支中的连接!
还有一个非常重要的点,**借用规则在分支表达式和结果处理中存在很大的不同**。例如上面代码中,我们在两个分支表达式中分别对 `data` 做了不可变借用这当然ok但是若是两次可变借用那编译器会立即进行报错。但是转折来了当在结果处理中进行两次可变借用时却不会报错大家可以思考下为什么提示下思考下分支在执行成后会发生什么。 还有一个非常重要的点,**借用规则在分支表达式和结果处理中存在很大的不同**。例如上面代码中,我们在两个分支表达式中分别对 `data` 做了不可变借用这当然ok但是若是两次可变借用那编译器会立即进行报错。但是转折来了当在结果处理中进行两次可变借用时却不会报错大家可以思考下为什么提示下思考下分支在执行完成后会发生什么?
```rust ```rust
use tokio::sync::oneshot; use tokio::sync::oneshot;
@ -540,7 +538,7 @@ async fn main() {
当第一次循环开始时, 第一个分支会立即完成,因为 `operation` 的参数是 `None`。当第一个分支执行完成时,`done` 会变成 `true`,此时第一个分支的条件将无法被满足,开始执行第二个分支。 当第一次循环开始时, 第一个分支会立即完成,因为 `operation` 的参数是 `None`。当第一个分支执行完成时,`done` 会变成 `true`,此时第一个分支的条件将无法被满足,开始执行第二个分支。
当第二个分支收到一个偶数时,`done` 会被修改为 `false`,且 `operation` 被设置了值。 此后再一次循环时,第一个分支会被执行,且 `oertation` 返回一个 `Some(2)`,因此会触发 `return` ,最终结束循环并返回。 当第二个分支收到一个偶数时,`done` 会被修改为 `false`,且 `operation` 被设置了值。 此后再一次循环时,第一个分支会被执行,且 `operation` 返回一个 `Some(2)`,因此会触发 `return` ,最终结束循环并返回。
这段代码引入了一个新的语法: `if !done`,在解释之前,先看看去掉后会如何: 这段代码引入了一个新的语法: `if !done`,在解释之前,先看看去掉后会如何:
```console ```console
@ -556,10 +554,10 @@ note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
那大家肯定有疑问了,既然 `operation` 不能再被调用了,我们该如何在有偶数值时,再回到第一个分支对其进行调用呢?答案就是 `operation.set(action(Some(v)));`,该操作会重新使用新的参数设置 `operation` 那大家肯定有疑问了,既然 `operation` 不能再被调用了,我们该如何在有偶数值时,再回到第一个分支对其进行调用呢?答案就是 `operation.set(action(Some(v)));`,该操作会重新使用新的参数设置 `operation`
## spawan 和 select! 的一些不同 ## spawn 和 select! 的一些不同
学到现在,相信大家对于 `tokio::spawn``select!` 已经非常熟悉,它们的共同点就是都可以并发的运行异步操作。 学到现在,相信大家对于 `tokio::spawn``select!` 已经非常熟悉,它们的共同点就是都可以并发的运行异步操作。
然而它们使用的策略大相径庭。 然而它们使用的策略大相径庭。
`tokio::spawan` 函数会启动新的任务来运行一个异步操作,每个任务都是一个独立的对象可以单独被 Tokio 调度运行,因此两个不同的任务的调度都是独立进行的,甚至于它们可能会运行在两个不同的操作系统线程上。鉴于此,生成的任务和生成的线程有一个相同的限制:不允许对外部环境中的值进行借用。 `tokio::spawn` 函数会启动新的任务来运行一个异步操作,每个任务都是一个独立的对象可以单独被 Tokio 调度运行,因此两个不同的任务的调度都是独立进行的,甚至于它们可能会运行在两个不同的操作系统线程上。鉴于此,生成的任务和生成的线程有一个相同的限制:不允许对外部环境中的值进行借用。
`select!` 宏就不一样了,它在同一个任务中并发运行所有的分支,正是因为这样在同一个任务中,这些分支无法被同时运行。 `select!` 宏在单个任务中实现了多路复用的功能。 `select!` 宏就不一样了,它在同一个任务中并发运行所有的分支。正是因为这样,在同一个任务中,这些分支无法被同时运行。 `select!` 宏在单个任务中实现了多路复用的功能。

Loading…
Cancel
Save