Merge pull request #1182 from iscas-zac/main

Update web-server.md:steam->stream, collecto->collect
pull/1196/head
Sunface 2 years ago committed by GitHub
commit 25d496a9be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -430,7 +430,7 @@ error: could not compile `hello` due to previous error
报错就解决呗,但 Rust 中的 channel 实现是 mpsc即多生产者单消费者因此我们无法通过克隆消费者的方式来修复这个错误。当然发送多条消息给多个接收者也不在考虑范畴该怎么办似乎陷入了绝境。
雪上加霜的是,就算 `receiver` 可以克隆,但是你得保证同一个时间只有一个`receiver` 能接收消息,否则一个任务可能同时被多个 `Worker` 执行,因此多个线程需要安全的共享和使用 `receiver`,等等,安全的共享?听上去 `Arc` 这个多所有权结构非常适合,互斥使用?貌似 `Mutex` 很适合,结合一下,`Arc<Mutext<T>>`,这不就是我们之前见过多次的线程安全类型吗?
雪上加霜的是,就算 `receiver` 可以克隆,但是你得保证同一个时间只有一个`receiver` 能接收消息,否则一个任务可能同时被多个 `Worker` 执行,因此多个线程需要安全的共享和使用 `receiver`,等等,安全的共享?听上去 `Arc` 这个多所有权结构非常适合,互斥使用?貌似 `Mutex` 很适合,结合一下,`Arc<Mutex<T>>`,这不就是我们之前见过多次的线程安全类型吗?
总之,`Arc` 允许多个 `Worker` 同时持有 `receiver`,而 `Mutex` 可以确保一次只有一个 `Worker` 能从 `receiver` 接收消息。

@ -41,7 +41,7 @@ fn main() {
`unwrap` 的使用是因为 `bind` 返回 `Result<T,E>`,毕竟监听是有可能报错的,例如:如果要监听 `80` 端口往往需要管理员权限;监听了同样的端口,等等。
`incoming` 会返回一个迭代器,它每一次迭代都会返回一个新的连接 `steam`(客户端发起web服务器监听接收),因此,接下来要做的就是从 `steam` 中读取数据,然后返回处理后的结果。
`incoming` 会返回一个迭代器,它每一次迭代都会返回一个新的连接 `stream`(客户端发起web服务器监听接收),因此,接下来要做的就是从 `stream` 中读取数据,然后返回处理后的结果。
细心的同学可能会注意到,代码中对 `stream` 还进行了一次 `unwrap` 处理,原因在于我们并不是在迭代一个一个连接,而是在迭代处理一个一个请求建立连接的尝试,而这种尝试可能会失败!例如,操作系统的最大连接数限制。

@ -2,9 +2,9 @@
在入门章节中,我们简单学习了该如何使用 `async/.await` 同时在后面也了解了一些底层原理,现在是时候继续深入了。
`async/.await`是 Rust 语法的一部分,它在遇到阻塞操作时( 例如 IO )会让出当前线程的所有权而不是阻塞当前线程,这样就允许当前线程继续去执行其它代码,最终实现并发。
`async/.await` 是 Rust 语法的一部分,它在遇到阻塞操作时( 例如 IO )会让出当前线程的所有权而不是阻塞当前线程,这样就允许当前线程继续去执行其它代码,最终实现并发。
有两种方式可以使用`async` `async fn`用于声明函数,`async { ... }`用于声明语句块,它们会返回一个实现 `Future` 特征的值:
有两种方式可以使用 `async` `async fn` 用于声明函数,`async { ... }` 用于声明语句块,它们会返回一个实现 `Future` 特征的值:
```rust
// `foo()`返回一个`Future<Output = u8>`,
@ -37,7 +37,7 @@ fn foo_expanded<'a>(x: &'a u8) -> impl Future<Output = u8> + 'a {
}
```
意味着 `async fn` 函数返回的 `Future` 必须满足以下条件: 当 `x` 依然有效时, 该 `Future` 就必须继续等待( `.await` ), 也就是说`x` 必须比 `Future`活得更久。
意味着 `async fn` 函数返回的 `Future` 必须满足以下条件: 当 `x` 依然有效时, 该 `Future` 就必须继续等待( `.await` ), 也就是说 `x` 必须比 `Future` 活得更久。
在一般情况下,在函数调用后就立即 `.await` 不会存在任何问题,例如`foo(&x).await`。但是,若 `Future` 被先存起来或发送到另一个任务或者线程,就可能存在问题了:
@ -108,7 +108,7 @@ async fn blocks() {
// 由于`async move`会捕获环境中的变量,因此只有一个`async move`语句块可以访问该变量,
// 由于 `async move` 会捕获环境中的变量,因此只有一个 `async move` 语句块可以访问该变量,
// 但是它也有非常明显的好处: 变量可以转移到返回的 Future 中,不再受借用生命周期的限制
fn move_block() -> impl Future<Output = ()> {
let my_string = "foo".to_string();
@ -147,7 +147,7 @@ trait Stream {
}
```
关于 `Stream` 的一个常见例子是消息通道( `futures` 包中的)的消费者 `Receiver`。每次有消息从 `Send` 端发送后,它都可以接收到一个 `Some(val)` 值, 一旦 `Send` 端关闭(drop),且消息通道中没有消息后,它会接收到一个 `None` 值。
关于 `Stream` 的一个常见例子是消息通道( `futures` 包中的)的消费者 `Receiver`。每次有消息从 `Send` 端发送后,它都可以接收到一个 `Some(val)` 值, 一旦 `Send` 端关闭( `drop` ),且消息通道中没有消息后,它会接收到一个 `None` 值。
```rust
async fn send_recv() {
@ -168,7 +168,7 @@ async fn send_recv() {
#### 迭代和并发
跟迭代器类似,我们也可以迭代一个 `Stream`。 例如使用`map``filter``fold`方法,以及它们的*遇到错误提前返回*的版本: `try_map``try_filter``try_fold`。
跟迭代器类似,我们也可以迭代一个 `Stream`。 例如使用 `map``filter``fold` 方法,以及它们的*遇到错误提前返回*的版本: `try_map``try_filter``try_fold`。
但是跟迭代器又有所不同,`for` 循环无法在这里使用,但是命令式风格的循环`while let`是可以用的,同时还可以使用`next` 和 `try_next` 方法:

@ -26,7 +26,7 @@ enum Poll<T> {
若在当前 `poll` 中, `Future` 可以被完成,则会返回 `Poll::Ready(result)` ,反之则返回 `Poll::Pending` 并且安排一个 `wake` 函数:当未来 `Future` 准备好进一步执行时, 该函数会被调用,然后管理该 `Future` 的执行器(例如上一章节中的`block_on`函数)会再次调用 `poll` 方法,此时 `Future` 就可以继续执行了。
如果没有 `wake `方法,那执行器无法知道某个`Future`是否可以继续被执行,除非执行器定期的轮询每一个 `Future` ,确认它是否能被执行,但这种作法效率较低。而有了 `wake``Future` 就可以主动通知执行器,然后执行器就可以精确的执行该 `Future`。 这种“事件通知 -> 执行”的方式要远比定期对所有 `Future` 进行一次全遍历来的高效。
如果没有 `wake` 方法,那执行器无法知道某个 `Future` 是否可以继续被执行,除非执行器定期的轮询每一个 `Future`,确认它是否能被执行,但这种作法效率较低。而有了 `wake``Future` 就可以主动通知执行器,然后执行器就可以精确的执行该 `Future`。 这种“事件通知 -> 执行”的方式要远比定期对所有 `Future` 进行一次全遍历来的高效。
也许大家还是迷迷糊糊的,没事,我们用一个例子来说明下。考虑一个需要从 `socket` 读取数据的场景:如果有数据,可以直接读取数据并返回 `Poll::Ready(data)` 但如果没有数据,`Future` 会被阻塞且不会再继续执行,此时它会注册一个 `wake` 函数,当 `socket` 数据准备好时,该函数将被调用以通知执行器:我们的 `Future` 已经准备好了,可以继续执行。
@ -74,7 +74,7 @@ enum Poll<T> {
/// 之所以可以并发是因为两个Future的轮询可以交替进行一个阻塞另一个就可以立刻执行反之亦然
pub struct Join<FutureA, FutureB> {
// 结构体的每个字段都包含一个Future可以运行直到完成.
// 如果Future完成后字段会被设置为 `None`. 这样Future完成后就不会再被轮询
// 等到Future完成后字段会被设置为 `None`. 这样Future完成后就不会再被轮询
a: Option<FutureA>,
b: Option<FutureB>,
}
@ -160,7 +160,7 @@ trait Future {
}
```
首先这里多了一个 `Pin` ,关于它我们会在后面章节详细介绍,现在你只需要知道使用它可以创建一个无法被移动的 `Future` ,因为无法被移动,因此它将具有固定的内存地址,意味着我们可以存储它的指针(如果内存地址可能会变动,那存储指针地址将毫无意义!),也意味着可以实现一个自引用数据结构: `struct MyFut { a: i32, ptr_to_a: *const i32 }`。 而对于 `async/await` 来说,`Pin` 是不可或缺的关键特性。
首先这里多了一个 `Pin` ,关于它我们会在后面章节详细介绍,现在你只需要知道使用它可以创建一个无法被移动的 `Future` ,因为无法被移动,所以它将具有固定的内存地址,意味着我们可以存储它的指针(如果内存地址可能会变动,那存储指针地址将毫无意义!),也意味着可以实现一个自引用数据结构: `struct MyFut { a: i32, ptr_to_a: *const i32 }`。 而对于 `async/await` 来说,`Pin` 是不可或缺的关键特性。
其次,从 `wake: fn()` 变成了 `&mut Context<'_>` 。意味着 `wake` 函数可以携带数据了,为何要携带数据?考虑一个真实世界的场景,一个复杂应用例如 web 服务器可能有数千连接同时在线,那么同时就有数千 `Future` 在被同时管理着,如果不能携带数据,当一个 `Future` 调用 `wake` 后,执行器该如何知道是哪个 `Future` 调用了 `wake` ,然后进一步去 `poll` 对应的 `Future` ?没有办法!那之前的例子为啥就可以使用没有携带数据的 `wake` 因为足够简单,不存在歧义性。
@ -266,7 +266,7 @@ impl TimerFuture {
Rust 的 `Future` 是惰性的:只有屁股上拍一拍,它才会努力动一动。其中一个推动它的方式就是在 `async` 函数中使用 `.await` 来调用另一个 `async` 函数,但是这个只能解决 `async` 内部的问题,那么这些最外层的 `async` 函数,谁来推动它们运行呢?答案就是我们之前多次提到的执行器 `executor`
执行器会管理一批 `Future` (最外层的 `async` 函数),然后通过不停地 `poll` 推动它们直到完成。 最开始,执行器会先 `poll` 一次 `Future` ,后面就不会主动去 `poll` 了,而是等待 `Future` 通过调用 `wake` 函数来通知它可以继续,它才会继续去 `poll` 。这种**wake 通知然后 poll**的方式会不断重复,直到 `Future` 完成。
执行器会管理一批 `Future` (最外层的 `async` 函数),然后通过不停地 `poll` 推动它们直到完成。 最开始,执行器会先 `poll` 一次 `Future` ,后面就不会主动去 `poll` 了,而是等待 `Future` 通过调用 `wake` 函数来通知它可以继续,它才会继续去 `poll` 。这种 **wake 通知然后 poll** 的方式会不断重复,直到 `Future` 完成。
#### 构建执行器
@ -496,4 +496,4 @@ let event = io_blocker.block();
println!("Socket {:?} is now {:?}", event.id, event.signals);
```
这样,我们只需要一个执行器线程,它会接收 IO 事件并将其分发到对应的 `Waker` 中,接着后者会唤醒相关的任务,最终通过执行器 `poll` 后,任务可以顺利继续执行, 这种 IO 读取流程可以不停的循环,直到 `socket` 关闭。
这样,我们只需要一个执行器线程,它会接收 IO 事件并将其分发到对应的 `Waker` 中,接着后者会唤醒相关的任务,最终通过执行器 `poll` 后,任务可以顺利继续执行, 这种 IO 读取流程可以不停的循环,直到 `socket` 关闭。

@ -56,7 +56,7 @@
> 若大家使用 tokio那 CPU 密集的任务尤其需要用线程的方式去处理,例如使用 `spawn_blocking` 创建一个阻塞的线程去完成相应 CPU 密集任务。
>
> 至于具体的原因不仅是上文说到的那些还有一个是tokio 是协作式调度器,如果某个 CPU 密集的异步任务是通过 tokio 创建的那理论上来说该异步任务需要跟其它的异步任务交错执行最终大家都得到了执行皆大欢喜。但实际情况是CPU 密集的任务很可能会一直霸占着 CPU此时 tokio 的调度方式决定了该任务会一直被执行,这意味着,其它的异步任务无法得到执行的机会,最终这些任务都会因为得不到资源而饿死。
> 至于具体的原因不仅是上文说到的那些还有一个是tokio 是协作式调度器,如果某个 CPU 密集的异步任务是通过 tokio 创建的那理论上来说该异步任务需要跟其它的异步任务交错执行最终大家都得到了执行皆大欢喜。但实际情况是CPU 密集的任务很可能会一直霸占着 CPU此时 tokio 的调度方式决定了该任务会一直被执行,这意味着,其它的异步任务无法得到执行的机会,最终这些任务都会因为得不到资源而饿死。
>
> 而使用 `spawn_blocking` 后,会创建一个单独的 OS 线程,该线程并不会被 tokio 所调度( 被 OS 所调度 ),因此它所执行的 CPU 密集任务也不会导致 tokio 调度的那些异步任务被饿死

@ -1,10 +1,10 @@
# 使用`join!`和`select!`同时运行多个 Future
# 使用 `join!` `select!` 同时运行多个 Future
招数单一,杀伤力惊人,说的就是 `.await` ,但是光用它,还真做不到一招鲜吃遍天。比如我们该如何同时运行多个任务,而不是使用`.await`慢悠悠地排队完成。
招数单一,杀伤力惊人,说的就是 `.await` ,但是光用它,还真做不到一招鲜吃遍天。比如我们该如何同时运行多个任务,而不是使用 `.await` 慢悠悠地排队完成。
## join!
`futures` 包中提供了很多实用的工具,其中一个就是 `join!`宏, 它允许我们同时等待多个不同 `Future` 的完成,且可以并发地运行这些 `Future`
`futures` 包中提供了很多实用的工具,其中一个就是 `join!` 宏, 它允许我们同时等待多个不同 `Future` 的完成,且可以并发地运行这些 `Future`
先来看一个不是很给力的、使用`.await`的版本:
@ -43,13 +43,13 @@ async fn enjoy_book_and_music() -> (Book, Music) {
}
```
`Duang`,目标顺利达成。同时`join!`会返回一个元组,里面的值是对应的`Future`执行结束后输出的值。
`Duang`,目标顺利达成。同时 `join!` 会返回一个元组,里面的值是对应的 `Future` 执行结束后输出的值。
> 如果希望同时运行一个数组里的多个异步任务,可以使用 `futures::future::join_all` 方法
## try_join!
由于`join!`必须等待它管理的所有 `Future` 完成后才能完成,如果你希望在某一个 `Future` 报错后就立即停止所有 `Future` 的执行,可以使用 `try_join!`,特别是当 `Future` 返回 `Result` 时:
由于 `join!` 必须等待它管理的所有 `Future` 完成后才能完成,如果你希望在某一个 `Future` 报错后就立即停止所有 `Future` 的执行,可以使用 `try_join!`,特别是当 `Future` 返回 `Result` 时:
```rust
use futures::try_join;
@ -64,7 +64,7 @@ async fn get_book_and_music() -> Result<(Book, Music), String> {
}
```
有一点需要注意,传给 `try_join!` 的所有 `Future` 都必须拥有相同的错误类型。如果错误类型不同,可以考虑使用来自 `futures::future::TryFutureExt` 模块的 `map_err`和`err_info`方法将错误进行转换:
有一点需要注意,传给 `try_join!` 的所有 `Future` 都必须拥有相同的错误类型。如果错误类型不同,可以考虑使用来自 `futures::future::TryFutureExt` 模块的 `map_err` `err_info` 方法将错误进行转换:
```rust
use futures::{
@ -82,11 +82,11 @@ async fn get_book_and_music() -> Result<(Book, Music), String> {
}
```
`join!`很好很强大但是人无完人J 无完 J, 它有一个很大的问题。
`join!` 很好很强大但是人无完人J 无完 J, 它有一个很大的问题。
## select!
`join!`只有等所有 `Future` 结束后,才能集中处理结果,如果你想同时等待多个 `Future` ,且任何一个 `Future` 结束后,都可以立即被处理,可以考虑使用 `futures::select!`:
`join!` 只有等所有 `Future` 结束后,才能集中处理结果,如果你想同时等待多个 `Future` ,且任何一个 `Future` 结束后,都可以立即被处理,可以考虑使用 `futures::select!`:
```rust
use futures::{
@ -119,8 +119,8 @@ async fn race_tasks() {
`select!`还支持 `default``complete` 分支:
- `complete` 分支当所有的 `Future``Stream` 完成后才会被执行,它往往配合`loop`使用,`loop`用于循环完成所有的 `Future`
- `default`分支,若没有任何 `Future``Stream` 处于 `Ready` 状态, 则该分支会被立即执行
- `complete` 分支当所有的 `Future``Stream` 完成后才会被执行,它往往配合 `loop` 使用,`loop` 用于循环完成所有的 `Future`
- `default` 分支,若没有任何 `Future``Stream` 处于 `Ready` 状态, 则该分支会被立即执行
```rust
use futures::future;
@ -135,16 +135,16 @@ pub fn main() {
a = a_fut => total += a,
b = b_fut => total += b,
complete => break,
default => panic!(), // 该分支永远不会运行,因为`Future`会先运行,然后是`complete`
default => panic!(), // 该分支永远不会运行,因为 `Future` 会先运行,然后是 `complete`
};
}
assert_eq!(total, 10);
}
```
以上代码 `default` 分支由于最后一个运行,而在它之前 `complete` 分支已经通过 `break` 跳出了循环,因此`default`永远不会被执行。
以上代码 `default` 分支由于最后一个运行,而在它之前 `complete` 分支已经通过 `break` 跳出了循环,因此 `default` 永远不会被执行。
如果你希望 `default` 也有机会露下脸,可以将 `complete``break` 修改为其它的,例如`println!("completed!")`,然后再观察下运行结果。
如果你希望 `default` 也有机会露下脸,可以将 `complete``break` 修改为其它的,例如 `println!("completed!")`,然后再观察下运行结果。
再回到 `select` 的第一个例子中,里面有一段代码长这样:
@ -159,14 +159,14 @@ pin_mut!(t1, t2);
#### 跟 `Unpin``FusedFuture` 进行交互
首先,`.fuse()`方法可以让 `Future` 实现 `FusedFuture` 特征, 而 `pin_mut!` 宏会为 `Future` 实现 `Unpin`特征,这两个特征恰恰是使用 `select` 所必须的:
首先,`.fuse()` 方法可以让 `Future` 实现 `FusedFuture` 特征, 而 `pin_mut!` 宏会为 `Future` 实现 `Unpin` 特征,这两个特征恰恰是使用 `select` 所必须的:
- `Unpin`,由于 `select` 不会通过拿走所有权的方式使用`Future`,而是通过可变引用的方式去使用,这样当 `select` 结束后,该 `Future` 若没有被完成,它的所有权还可以继续被其它代码使用。
- `FusedFuture`的原因跟上面类似,当 `Future` 一旦完成后,那 `select` 就不能再对其进行轮询使用。`Fuse`意味着熔断,相当于 `Future` 一旦完成,再次调用`poll`会直接返回`Poll::Pending`。
- `Unpin`,由于 `select` 不会通过拿走所有权的方式使用 `Future`,而是通过可变引用的方式去使用,这样当 `select` 结束后,该 `Future` 若没有被完成,它的所有权还可以继续被其它代码使用。
- `FusedFuture` 的原因跟上面类似,当 `Future` 一旦完成后,那 `select` 就不能再对其进行轮询使用。`Fuse` 意味着熔断,相当于 `Future` 一旦完成,再次调用 `poll` 会直接返回 `Poll::Pending`
只有实现了`FusedFuture``select` 才能配合 `loop` 一起使用。假如没有实现,就算一个 `Future` 已经完成了,它依然会被 `select` 不停的轮询执行。
只有实现了 `FusedFuture``select` 才能配合 `loop` 一起使用。假如没有实现,就算一个 `Future` 已经完成了,它依然会被 `select` 不停的轮询执行。
`Stream` 稍有不同,它们使用的特征是 `FusedStream`。 通过`.fuse()`(也可以手动实现)实现了该特征的 `Stream`,对其调用`.next()` 或 `.try_next()`方法可以获取实现了`FusedFuture`特征的`Future`:
`Stream` 稍有不同,它们使用的特征是 `FusedStream`。 通过 `.fuse()`(也可以手动实现)实现了该特征的 `Stream`,对其调用 `.next()``.try_next()` 方法可以获取实现了 `FusedFuture` 特征的`Future`:
```rust
use futures::{
@ -199,7 +199,7 @@ async fn add_two_streams(
一个很实用但又鲜为人知的函数是 `Fuse::terminated()` ,可以使用它构建一个空的 `Future` ,空自然没啥用,但是如果它能在后面再被填充呢?
考虑以下场景:当你要在`select`循环中运行一个任务,但是该任务却是在`select`循环内部创建时,上面的函数就非常好用了。
考虑以下场景:当你要在 `select` 循环中运行一个任务,但是该任务却是在 `select` 循环内部创建时,上面的函数就非常好用了。
```rust
use futures::{
@ -272,13 +272,13 @@ async fn run_loop(
loop {
select! {
() = interval_timer.select_next_some() => {
// 定时器已结束,若`get_new_num_fut`没有在运行,就创建一个新的
// 定时器已结束,若 `get_new_num_fut` 没有在运行,就创建一个新的
if get_new_num_fut.is_terminated() {
get_new_num_fut.set(get_new_num().fuse());
}
},
new_num = get_new_num_fut => {
// 收到新的数字 -- 创建一个新的`run_on_new_num_fut` (并没有像之前的例子那样丢弃掉旧值)
// 收到新的数字 -- 创建一个新的 `run_on_new_num_fut` (并没有像之前的例子那样丢弃掉旧值)
run_on_new_num_futs.push(run_on_new_num(new_num));
},
// 运行 `run_on_new_num_futs`, 并检查是否有已经完成的

@ -35,7 +35,7 @@ error[E0282]: type annotations needed
| ^^ cannot infer type for type parameter `E` declared on the enum `Result`
```
原因在于编译器无法推断出 `Result<T, E>`中的 `E` 的类型, 而且编译器的提示```consider giving `fut` a type```你也别傻乎乎的相信,然后尝试半天,最后无奈放弃:目前还没有办法为 `async` 语句块指定返回类型。
原因在于编译器无法推断出 `Result<T, E>` 中的 `E` 的类型, 而且编译器的提示 ```consider giving `fut` a type``` 你也别傻乎乎的相信,然后尝试半天,最后无奈放弃:目前还没有办法为 `async` 语句块指定返回类型。
既然编译器无法推断出类型,那咱就给它更多提示,可以使用 `::< ... >` 的方式来增加类型注释:
@ -47,13 +47,13 @@ let fut = async {
};
```
给予类型注释后此时编译器就知道`Result<T, E>`中的 `E` 的类型是`String`,进而成功通过编译。
给予类型注释后此时编译器就知道 `Result<T, E>` 中的 `E` 的类型是 `String`,进而成功通过编译。
## async 函数和 Send 特征
在多线程章节我们深入讲过 `Send` 特征对于多线程间数据传递的重要性,对于 `async fn` 也是如此,它返回的 `Future` 能否在线程间传递的关键在于 `.await` 运行过程中,作用域中的变量类型是否是 `Send`
学到这里,相信大家已经很清楚`Rc`无法在多线程环境使用,原因就在于它并未实现 `Send` 特征,那咱就用它来做例子:
学到这里,相信大家已经很清楚 `Rc` 无法在多线程环境使用,原因就在于它并未实现 `Send` 特征,那咱就用它来做例子:
```rust
use std::rc::Rc;
@ -78,7 +78,7 @@ fn main() {
}
```
即使上面的 `foo` 返回的 `Future``Send` 但是在它内部短暂的使用 `NotSend` 依然是安全的,原因在于它的作用域并没有影响到 `.await`,下面来试试声明一个变量,然后让 `.await`的调用处于变量的作用域中试试:
即使上面的 `foo` 返回的 `Future``Send` 但是在它内部短暂的使用 `NotSend` 依然是安全的,原因在于它的作用域并没有影响到 `.await`,下面来试试声明一个变量,然后让 `.await` 的调用处于变量的作用域中试试:
```rust
async fn foo() {
@ -127,7 +127,7 @@ async fn foo() {
## 递归使用 async fn
在内部实现中,`async fn`被编译成一个状态机,这会导致递归使用 `async fn` 变得较为复杂, 因为编译后的状态机还需要包含自身。
在内部实现中,`async fn` 被编译成一个状态机,这会导致递归使用 `async fn` 变得较为复杂, 因为编译后的状态机还需要包含自身。
```rust
// foo函数:
@ -141,7 +141,7 @@ enum Foo {
Second(StepTwo),
}
// 因此recursive函数
// 因此 recursive 函数
async fn recursive() {
recursive().await;
recursive().await;
@ -166,7 +166,7 @@ error[E0733]: recursion in an `async fn` requires boxing
= note: a recursive `async fn` must be rewritten to return a boxed future.
```
如果认真学过之前的章节,大家应该知道只要将其使用 `Box` 放到堆上而不是栈上,就可以解决,在这里还是要称赞下 Rust 的编译器,给出的提示总是这么精确```recursion in an `async fn` requires boxing```。
如果认真学过之前的章节,大家应该知道只要将其使用 `Box` 放到堆上而不是栈上,就可以解决,在这里还是要称赞下 Rust 的编译器,给出的提示总是这么精确 ```recursion in an `async fn` requires boxing```。
就算是使用 `Box`,这里也大有讲究。如果我们试图使用 `Box::pin` 这种方式去包裹是不行的,因为编译器自身的限制限制了我们(刚夸过它。。。)。为了解决这种问题,我们只能将 `recursive` 转变成一个正常的函数,该函数返回一个使用 `Box` 包裹的 `async` 语句块:
@ -245,5 +245,5 @@ impl Advertisement for AutoplayingVideo {
}
```
不过使用该包并不是免费的,每一次特征中的`async`函数被调用时,都会产生一次堆内存分配。对于大多数场景,这个性能开销都可以接受,但是当函数一秒调用几十万、几百万次时,就得小心这块儿代码的性能了!
不过使用该包并不是免费的,每一次特征中的 `async` 函数被调用时,都会产生一次堆内存分配。对于大多数场景,这个性能开销都可以接受,但是当函数一秒调用几十万、几百万次时,就得小心这块儿代码的性能了!

@ -1,6 +1,6 @@
# 定海神针 Pin 和 Unpin
在 Rust 异步编程中,有一个定海神针般的存在,它就是 `Pin` ,作用说简单也简单,说复杂也非常复杂,当初刚出来时就连一些 Rust 大佬都一头雾水,何况瑟瑟发抖的我。好在今非昔比,目前网上的资料已经很全,而我就借花献佛,给大家好好讲讲这个`Pin`。
在 Rust 异步编程中,有一个定海神针般的存在,它就是 `Pin`,作用说简单也简单,说复杂也非常复杂,当初刚出来时就连一些 Rust 大佬都一头雾水,何况瑟瑟发抖的我。好在今非昔比,目前网上的资料已经很全,而我就借花献佛,给大家好好讲讲这个 `Pin`
在 Rust 中,所有的类型可以分为两类:
@ -297,7 +297,7 @@ impl Test {
> BTW, Rust 中的 unsafe 其实没有那么可怕,虽然听上去很不安全,但是实际上 Rust 依然提供了很多机制来帮我们提升了安全性,因此不必像对待 Go 语言的 `unsafe` 那样去畏惧于使用 Rust 中的 `unsafe` ,大致使用原则总结如下:没必要用时,就不要用,当有必要用时,就大胆用,但是尽量控制好边界,让 `unsafe` 的范围尽可能小
此时,再去尝试移动被固定的值,就会导致**编译错误**
此时,再去尝试移动被固定的值,就会导致**编译错误**
```rust
pub fn main() {
@ -329,7 +329,7 @@ error[E0277]: `PhantomPinned` cannot be unpinned
> 需要注意的是固定在栈上非常依赖于你写出的 `unsafe` 代码的正确性。我们知道 `&'a mut T` 可以固定的生命周期是 `'a` ,但是我们却不知道当生命周期 `'a` 结束后,该指针指向的数据是否会被移走。如果你的 `unsafe` 代码里这么实现了,那么就会违背 `Pin` 应该具有的作用!
>
> 一个常见的错误就是忘记去遮蔽(shadow )初始的变量,因为你可以 `drop``Pin` ,然后在 `&'a mut T` 结束后去移动数据:
> 一个常见的错误就是忘记去[遮蔽( shadow )](https://course.rs/basic/variable.html#%E5%8F%98%E9%87%8F%E9%81%AE%E8%94%BDshadowing)初始的变量,因为你可以 `drop``Pin` ,然后在 `&'a mut T` 结束后去移动数据:
>
> ```rust
> fn main() {
@ -475,4 +475,4 @@ execute_unpin_future(fut); // OK
- 可以将值固定到栈上,也可以固定到堆上
- 将 `!Unpin` 值固定到栈上需要使用 `unsafe`
- 将 `!Unpin` 值固定到堆上无需 `unsafe` ,可以通过 `Box::pin` 来简单的实现
- 当固定类型`T: !Unpin`时,你需要保证数据从被固定到被 drop 这段时期内,其内存不会变得非法或者被重用
- 当固定类型 `T: !Unpin` 时,你需要保证数据从被固定到被 drop 这段时期内,其内存不会变得非法或者被重用

@ -34,7 +34,7 @@ fn handle_connection(mut stream: TcpStream) {
let get = b"GET / HTTP/1.1\r\n";
// 处理HTTP协议头若不符合则返回404和对应的`html`文件
// 处理HTTP协议头若不符合则返回404和对应的 `html` 文件
let (status_line, filename) = if buffer.starts_with(get) {
("HTTP/1.1 200 OK\r\n\r\n", "hello.html")
} else {
@ -45,7 +45,7 @@ fn handle_connection(mut stream: TcpStream) {
// 将回复内容写入连接缓存中
let response = format!("{status_line}{contents}");
stream.write_all(response.as_bytes()).unwrap();
// 使用flush将缓存中的内容发送到客户端
// 使用 flush 将缓存中的内容发送到客户端
stream.flush().unwrap();
}
```
@ -98,13 +98,13 @@ async fn handle_connection(mut stream: TcpStream) {
}
```
该修改会将函数的返回值从 `()` 变成 `Future<Output=()>` ,因此直接运行将不再有任何效果,只用通过`.await`或执行器的`poll`调用后才能获取 `Future` 的结果。
该修改会将函数的返回值从 `()` 变成 `Future<Output=()>` ,因此直接运行将不再有任何效果,只用通过 `.await` 或执行器的 `poll` 调用后才能获取 `Future` 的结果。
在之前的代码中,我们使用了自己实现的简单的执行器来进行`.await` 或 `poll` ,实际上这只是为了学习原理,**在实际项目中,需要选择一个三方的 `async` 运行时来实现相关的功能**。 具体的选择我们将在下一章节进行讲解,现在先选择 `async-std` ,该包的最大优点就是跟标准库的 API 类似,相对来说更简单易用。
在之前的代码中,我们使用了自己实现的简单的执行器来进行 `.await``poll` ,实际上这只是为了学习原理,**在实际项目中,需要选择一个三方的 `async` 运行时来实现相关的功能**。 具体的选择我们将在下一章节进行讲解,现在先选择 `async-std` ,该包的最大优点就是跟标准库的 API 类似,相对来说更简单易用。
#### 使用 async-std 作为异步运行时
#### 使用 `async-std` 作为异步运行时
下面的例子将演示如何使用一个异步运行时`async-std`来让之前的 `async fn` 函数运行起来,该运行时允许使用属性 `#[async_std::main]` 将我们的 `fn main` 函数变成 `async fn main` ,这样就可以在 `main` 函数中直接调用其它 `async` 函数,否则你得用之前章节的 `block_on` 方法来让 `main` 去阻塞等待异步函数的完成,但是这种简单粗暴的阻塞等待方式并不灵活。
下面的例子将演示如何使用一个异步运行时 `async-std` 来让之前的 `async fn` 函数运行起来,该运行时允许使用属性 `#[async_std::main]` 将我们的 `fn main` 函数变成 `async fn main` ,这样就可以在 `main` 函数中直接调用其它 `async` 函数,否则你得用之前章节的 `block_on` 方法来让 `main` 去阻塞等待异步函数的完成,但是这种简单粗暴的阻塞等待方式并不灵活。
修改 `Cargo.toml` 添加 `async-std` 包并开启相应的属性:
@ -167,7 +167,7 @@ async fn handle_connection(mut stream: TcpStream) {
## 并发地处理连接
上面代码最大的问题是 `listener.incoming()` 是阻塞的迭代器。当 `listener` 在等待连接时,执行器是无法执行其它`Future`的,而且只有在我们处理完已有的连接后,才能接收新的连接。
上面代码最大的问题是 `listener.incoming()` 是阻塞的迭代器。当 `listener` 在等待连接时,执行器是无法执行其它 `Future` 的,而且只有在我们处理完已有的连接后,才能接收新的连接。
解决方法是将 `listener.incoming()` 从一个阻塞的迭代器变成一个非阻塞的 `Stream` 后者在前面章节有过专门介绍:
@ -235,7 +235,7 @@ async fn main() {
至此,我们实现了同时使用并行(多线程)和并发( `async` )来同时处理多个请求!
## 测试 handle_connection 函数
## 测试 `handle_connection` 函数
对于测试 Web 服务器,使用集成测试往往是最简单的,但是在本例子中,将使用单元测试来测试连接处理函数的正确性。

Loading…
Cancel
Save