diff --git a/listings/ch20-web-server/listing-20-13/src/lib.rs b/listings/ch20-web-server/listing-20-13/src/lib.rs index e60c902..248fb52 100755 --- a/listings/ch20-web-server/listing-20-13/src/lib.rs +++ b/listings/ch20-web-server/listing-20-13/src/lib.rs @@ -2,13 +2,13 @@ pub struct ThreadPool; // ANCHOR: here impl ThreadPool { - /// Create a new ThreadPool. + /// 创建线程池。 /// - /// The size is the number of threads in the pool. + /// 线程池中线程的数量。 /// /// # Panics /// - /// The `new` function will panic if the size is zero. + /// `new` 函数在 size 为 0 时会 panic。 pub fn new(size: usize) -> ThreadPool { assert!(size > 0); diff --git a/src/appendix-01-keywords.md b/src/appendix-01-keywords.md index a10a67c..ef09912 100644 --- a/src/appendix-01-keywords.md +++ b/src/appendix-01-keywords.md @@ -2,18 +2,20 @@ > [appendix-01-keywords.md](https://github.com/rust-lang/book/blob/main/src/appendix-01-keywords.md) >
-> commit 27dd97a785794709aa87c51ab697cded41e8163a +> commit de7174e6704ee4f6c8cdaead2c5c47e593775ec5 下面的列表包含 Rust 中正在使用或者以后会用到的关键字。因此,这些关键字不能被用作标识符(除了 “[原始标识符][raw-identifiers]” 部分介绍的原始标识符),这包括函数、变量、参数、结构体字段、模块、crate、常量、宏、静态值、属性、类型、trait 或生命周期 的名字。 -[raw-identifiers]: #raw-identifiers +[raw-identifiers]: #原始标识符 ### 目前正在使用的关键字 如下关键字目前有对应其描述的功能。 * `as` - 强制类型转换,消除特定包含项的 trait 的歧义,或者对 `use` 和 `extern crate` 语句中的项重命名 +* `async` - 返回一个 `Future` 而不是阻塞当前线程 +* `await` - 暂停执行直到 `Future` 的结果就绪 * `break` - 立刻退出循环 * `const` - 定义常量或不变裸指针(constant raw pointer) * `continue` - 继续进入下一次循环迭代 @@ -37,7 +39,7 @@ * `pub` - 表示结构体字段、`impl` 块或模块的公有可见性 * `ref` - 通过引用绑定 * `return` - 从函数中返回 -* `Self` - 实现 trait 的类型的类型别名 +* `Self` - 定义或实现 trait 的类型的类型别名 * `self` - 表示方法本身或当前模块 * `static` - 表示全局变量或在整个程序执行期间保持其生命周期 * `struct` - 定义一个结构体 @@ -45,18 +47,19 @@ * `trait` - 定义一个 trait * `true` - 布尔字面值 `true` * `type` - 定义一个类型别名或关联类型 +* `union` - 定义一个 [union] 并且是 union 声明中唯一用到的关键字 * `unsafe` - 表示不安全的代码、函数、trait 或实现 * `use` - 引入外部空间的符号 * `where` - 表示一个约束类型的从句 * `while` - 基于一个表达式的结果判断是否进行循环 +[union]: https://doc.rust-lang.org/reference/items/unions.html + ### 保留做将来使用的关键字 如下关键字没有任何功能,不过由 Rust 保留以备将来的应用。 * `abstract` -* `async` -* `await` * `become` * `box` * `do` diff --git a/src/appendix-02-operators.md b/src/appendix-02-operators.md index 5f3d287..7489051 100644 --- a/src/appendix-02-operators.md +++ b/src/appendix-02-operators.md @@ -2,7 +2,7 @@ > [appendix-02-operators.md](https://github.com/rust-lang/book/blob/main/src/appendix-02-operators.md) >
-> commit 426f3e4ec17e539ae9905ba559411169d303a031 +> commit f83c4d08d2bc9c1e1c33037747ffe818b397e67a 该附录包含了 Rust 语法的词汇表,包括运算符以及其他的符号,这些符号单独出现或出现在路径、泛型、trait bounds、宏、属性、注释、元组以及大括号上下文中。 @@ -23,10 +23,10 @@ | `&` | `&type`, `&mut type`, `&'a type`, `&'a mut type` | 借用指针类型 | | | `&` | `expr & expr` | 按位与 | `BitAnd` | | `&=` | `var &= expr` | 按位与及赋值 | `BitAndAssign` | -| `&&` | `expr && expr` | 逻辑与 | | +| `&&` | `expr && expr` | 短路(Short-circuiting)逻辑与 | | | `*` | `expr * expr` | 算术乘法 | `Mul` | | `*=` | `var *= expr` | 算术乘法与赋值 | `MulAssign` | -| `*` | `*expr` | 解引用 | | +| `*` | `*expr` | 解引用 | `Deref` | | `*` | `*const type`, `*mut type` | 裸指针 | | | `+` | `trait + trait`, `'a + trait` | 复合类型限制 | | | `+` | `expr + expr` | 算术加法 | `Add` | @@ -37,10 +37,11 @@ | `-=` | `var -= expr` | 算术减法与赋值 | `SubAssign` | | `->` | `fn(...) -> type`, |...| -> type | 函数与闭包,返回类型 | | | `.` | `expr.ident` | 成员访问 | | -| `..` | `..`, `expr..`, `..expr`, `expr..expr` | 右排除范围 | | +| `..` | `..`, `expr..`, `..expr`, `expr..expr` | 右开区间范围 | `PartialOrd` | +| `..=` | `..=expr`, `expr..=expr` | 右闭区间范围模式 | `PartialOrd` | | `..` | `..expr` | 结构体更新语法 | | -| `..` | `variant(x, ..)`, `struct_type { x, .. }` | “与剩余部分”的模式绑定 | | -| `...` | `expr...expr` | 模式: 范围包含模式 | | +| `..` | `variant(x, ..)`, `struct_type { x, .. }` | “与剩余部分” 的模式绑定 | | +| `...` | `expr...expr` | (Deprecated,请使用 `..=`)在模式中: 闭区间范围模式 | | | `/` | `expr / expr` | 算术除法 | `Div` | | `/=` | `var /= expr` | 算术除法与赋值 | `DivAssign` | | `:` | `pat: type`, `ident: type` | 约束 | | @@ -65,7 +66,7 @@ | | | pat | pat | 模式选择 | | | | | expr | expr | 按位或 | `BitOr` | | |= | var |= expr | 按位或与赋值 | `BitOrAssign` | -| || | expr || expr | 逻辑或 | | +| || | expr || expr | 短路(Short-circuiting)逻辑或 | | | `?` | `expr?` | 错误传播 | | ### 非运算符符号 @@ -130,7 +131,7 @@ |--------|-------------| | `T: U` | 泛型参数 `T` 约束于实现了 `U` 的类型 | | `T: 'a` | 泛型 `T` 的生命周期必须长于 `'a`(意味着该类型不能传递包含生命周期短于 `'a` 的任何引用)| -| `T : 'static` | 泛型 T 不包含除 'static 之外的借用引用 | +| `T: 'static` | 泛型 T 不包含除 'static 之外的借用引用 | | `'b: 'a` | 泛型 `'b` 生命周期必须长于泛型 `'a` | | `T: ?Sized` | 使用一个不定大小的泛型类型 | | `'a + trait`, `trait + trait` | 复合类型限制 | @@ -146,6 +147,7 @@ | `$ident` | 宏替换 | | `$ident:kind` | 宏捕获 | | `$(…)…` | 宏重复 | +| `ident!(...)`, `ident!{...}`, `ident![...]` | 宏调用 | 表 B-7 展示了写注释的符号。 @@ -173,7 +175,6 @@ | `(expr, ...)` | 元组表达式 | | `(type, ...)` | 元组类型 | | `expr(expr, ...)` | 函数调用表达式;也用于初始化元组结构体 `struct` 以及元组枚举 `enum` 变体 | -| `ident!(...)`, `ident!{...}`, `ident![...]` | 宏调用 | | `expr.0`, `expr.1`, etc. | 元组索引 | 表 B-9 展示了使用大括号的上下文。 diff --git a/src/appendix-03-derivable-traits.md b/src/appendix-03-derivable-traits.md index 9c6f66d..c1972bb 100644 --- a/src/appendix-03-derivable-traits.md +++ b/src/appendix-03-derivable-traits.md @@ -1,18 +1,17 @@ ## 附录 C:可派生的 trait -> [appendix-03-derivable-traits.md](https://github.com/rust-lang/book/blob/main/src/appendix-03-derivable-traits.md) ->
-> commit 426f3e4ec17e539ae9905ba559411169d303a031 +> [appendix-03-derivable-traits.md](https://github.com/rust-lang/book/blob/main/src/appendix-03-derivable-traits.md) >
+> commit bb8f6cc9ba2724e8363823bb6bf176cd33584548 在本书的各个部分中,我们讨论了可应用于结构体和枚举定义的 `derive` 属性。`derive` 属性会在使用 `derive` 语法标记的类型上生成对应 trait 的默认实现的代码。 在本附录中提供了标准库中所有可以使用 `derive` 的 trait 的参考。这些部分涉及到: -* 该 trait 将会派生什么样的操作符和方法 -* 由 `derive` 提供什么样的 trait 实现 -* 由什么来实现类型的 trait -* 是否允许实现该 trait 的条件 -* 需要 trait 操作的例子 +- 该 trait 将会派生什么样的操作符和方法 +- 由 `derive` 提供什么样的 trait 实现 +- 由什么来实现类型的 trait +- 是否允许实现该 trait 的条件 +- 需要 trait 操作的例子 如果你希望不同于 `derive` 属性所提供的行为,请查阅 [标准库文档](https://doc.rust-lang.org/std/index.html) 中每个 trait 的细节以了解如何手动实现它们。 @@ -50,7 +49,7 @@ 当在结构体上派生时,`PartialOrd` 以在结构体定义中字段出现的顺序比较每个字段的值来比较两个实例。当在枚举上派生时,认为在枚举定义中声明较早的枚举变体小于其后的变体。 -例如,对于来自于 `rand` crate 中的 `gen_range` 方法来说,当在一个大值和小值指定的范围内生成一个随机值时,`PartialOrd` trait 是必须的。 +例如,对于来自于 `rand` crate 中的 `gen_range` 方法来说,当在一个范围表达式指定的范围内生成一个随机值时,`PartialOrd` trait 是必须的。 `Ord` trait 也让你明白在一个带注解类型上的任意两个值存在有效顺序。`Ord` trait 实现了 `cmp` 方法,它返回一个 `Ordering` 而不是 `Option`,因为总存在一个合法的顺序。只可以在实现了 `PartialOrd` 和 `Eq`(`Eq` 依赖 `PartialEq`)的类型上使用 `Ord` trait 。当在结构体或枚举上派生时, `cmp` 和以 `PartialOrd` 派生实现的 `partial_cmp` 表现一致。 @@ -58,7 +57,7 @@ ### 复制值的 `Clone` 和 `Copy` -`Clone` trait 可以明确地创建一个值的深拷贝(deep copy),复制过程可能包含任意代码的执行以及堆上数据的复制。查阅第四章 [“变量和数据的交互方式:移动”][ways-variables-and-data-interact-clone] 以获取有关 `Clone` 的更多信息。 +`Clone` trait 可以明确地创建一个值的深拷贝(deep copy),复制过程可能包含任意代码的执行以及堆上数据的复制。查阅第四章 [“变量与数据交互的方式(二):克隆”][ways-variables-and-data-interact-clone] 以获取有关 `Clone` 的更多信息。 派生 `Clone` 实现了 `clone` 方法,其为整个的类型实现时,在类型的每一部分上调用了 `clone` 方法。这意味着类型中所有字段或值也必须实现了 `Clone`,这样才能够派生 `Clone` 。 @@ -68,7 +67,7 @@ `Copy` trait 并未定义任何方法来阻止编程人员重写这些方法或违反不需要执行额外代码的假设。尽管如此,所有的编程人员可以假设复制(copy)一个值非常快。 -可以在类型内部全部实现 `Copy` trait 的任意类型上派生 `Copy`。 但只可以在那些同时实现了 `Clone` 的类型上使用 `Copy` trait ,因为一个实现了 `Copy` 的类型也简单地实现了 `Clone`,其执行和 `Copy` 相同的任务。 +可以在类型内部全部实现 `Copy` trait 的任意类型上派生 `Copy`。一个实现了 `Copy` 的类型必须也实现了 `Clone`,因为一个实现了 `Copy` 的类型也简单地实现了 `Clone`,其执行和 `Copy` 相同的任务。 `Copy` trait 很少使用;实现 `Copy` 的类型是可以优化的,这意味着你无需调用 `clone`,这让代码更简洁。 @@ -86,12 +85,9 @@ `Default::default` 函数通常结合结构体更新语法一起使用,这在第五章的 [“使用结构体更新语法从其他实例中创建实例”][creating-instances-from-other-instances-with-struct-update-syntax] 部分有讨论。可以自定义一个结构体的一小部分字段而剩余字段则使用 `..Default::default()` 设置为默认值。 -例如,当你在 `Option` 实例上使用 `unwrap_or_default` 方法时,`Default` trait是必须的。如果 `Option` 是 `None`的话, `unwrap_or_default` 方法将返回存储在 `Option` 中 `T` 类型的 `Default::default` 的结果。 +例如,当你在 `Option` 实例上使用 `unwrap_or_default` 方法时,`Default` trait 是必须的。如果 `Option` 是 `None`的话, `unwrap_or_default` 方法将返回存储在 `Option` 中 `T` 类型的 `Default::default` 的结果。 -[creating-instances-from-other-instances-with-struct-update-syntax]: -ch05-01-defining-structs.html#creating-instances-from-other-instances-with-struct-update-syntax -[stack-only-data-copy]: -ch04-01-what-is-ownership.html#stack-only-data-copy -[ways-variables-and-data-interact-clone]: -ch04-01-what-is-ownership.html#ways-variables-and-data-interact-clone -[macros]: ch19-06-macros.html#macros +[creating-instances-from-other-instances-with-struct-update-syntax]: ch05-01-defining-structs.html#使用结构体更新语法从其他实例创建实例 +[stack-only-data-copy]: ch04-01-what-is-ownership.html#只在栈上的数据拷贝 +[ways-variables-and-data-interact-clone]: ch04-01-what-is-ownership.html#变量与数据交互的方式二克隆 +[macros]: ch19-06-macros.html#宏 diff --git a/src/appendix-04-useful-development-tools.md b/src/appendix-04-useful-development-tools.md index 55d39b1..0335f96 100644 --- a/src/appendix-04-useful-development-tools.md +++ b/src/appendix-04-useful-development-tools.md @@ -1,7 +1,6 @@ ## 附录 D:实用开发工具 -> [appendix-04-useful-development-tools.md](https://github.com/rust-lang/book/blob/main/src/appendix-04-useful-development-tools.md) ->
+> [appendix-04-useful-development-tools.md](https://github.com/rust-lang/book/blob/main/src/appendix-04-useful-development-tools.md) >
> commit d48e9884f4e5ecb112095d4e8c55ebc3bce4b009 本附录,我们将讨论 Rust 项目提供的用于开发 Rust 代码的工具。 @@ -24,7 +23,7 @@ $ cargo fmt 运行此命令会格式化当前 crate 中所有的 Rust 代码。这应该只会改变代码风格,而不是代码语义。请查看 [该文档][rustfmt] 了解 `rustfmt` 的更多信息。 -[rustfmt]: https://github.com/rust-lang-nursery/rustfmt +[rustfmt]: https://github.com/rust-lang/rustfmt ### 通过 `rustfix` 修复代码 @@ -67,7 +66,7 @@ $ cargo fix Finished dev [unoptimized + debuginfo] target(s) in 0.59s ``` -如果再次查看 *src/main.rs*,会发现 `cargo fix` 修改了代码: +如果再次查看 _src/main.rs_,会发现 `cargo fix` 修改了代码: 文件名: src/main.rs diff --git a/src/appendix-05-editions.md b/src/appendix-05-editions.md index cfbb35a..bf18e02 100644 --- a/src/appendix-05-editions.md +++ b/src/appendix-05-editions.md @@ -1,8 +1,7 @@ ## 附录 E:版本 -> [appendix-05-editions.md](https://github.com/rust-lang/book/blob/main/src/appendix-05-editions.md) ->
-> commit 70a82519e48b8a61f98cabb8ff443d1b21962fea +> [appendix-05-editions.md](https://github.com/rust-lang/book/blob/main/src/appendix-05-editions.md) >
+> commit 8cf0496bb8e56b683ea3f015871c8631684decf4 早在第一章,我们见过 `cargo new` 在 *Cargo.toml* 中增加了一些有关 `edition` 的元数据。本附录将解释其意义! @@ -12,11 +11,11 @@ Rust 语言和编译器有一个为期 6 周的发布循环。这意味着用户 这为不同的人群提供了不同的功能: -* 对于活跃的 Rust 用户,其将增量的修改与易于理解的功能包相结合。 -* 对于非用户,它表明发布了一些重大进展,这意味着 Rust 可能变得值得一试。 -* 对于 Rust 自身开发者,其提供了项目整体的集合点。 +- 对于活跃的 Rust 用户,其将增量的修改与易于理解的功能包相结合。 +- 对于非用户,它表明发布了一些重大进展,这意味着 Rust 可能变得值得一试。 +- 对于 Rust 自身开发者,其提供了项目整体的集合点。 -在本文档编写时,Rust 有两个版本:Rust 2015 和 Rust 2018。本书基于 Rust 2018 edition 编写。 +在本文档编写时,Rust 有三个可用版本:Rust 2015、 Rust 2018 和 Rust 2021。本书基于 Rust 2021 edition 风格编写。 *Cargo.toml* 中的 `edition` 字段表明代码应该使用哪个版本编译。如果该字段不存在,其默认为 `2015` 以提供后向兼容性。 diff --git a/src/appendix-06-translation.md b/src/appendix-06-translation.md index f0587ed..28f625e 100644 --- a/src/appendix-06-translation.md +++ b/src/appendix-06-translation.md @@ -2,7 +2,7 @@ > [appendix-06-translation.md](https://github.com/rust-lang/book/blob/main/src/appendix-06-translation.md) >
-> commit 72900e05f04ae60e06c2665567771bdd8befa89c +> commit 8b89742623513647ba289e0cf96edca4fe6fb022 一些非英语语言的资源。多数仍在翻译中;查阅 [翻译标签][label] 来帮助我们或使我们知道新的翻译! @@ -11,18 +11,22 @@ - [Português](https://github.com/rust-br/rust-book-pt-br) (BR) - [Português](https://github.com/nunojesus/rust-book-pt-pt) (PT) - [简体中文](https://github.com/KaiserY/trpl-zh-cn) +- [正體中文](https://github.com/rust-tw/book-tw) - [Українська](https://github.com/pavloslav/rust-book-uk-ua) - [Español](https://github.com/thecodix/book), [alternate](https://github.com/ManRR/rust-book-es) -- [Italiano](https://github.com/AgeOfWar/rust-book-it) -- [Русский](https://github.com/ruRust/rust_book_2ed) +- [Italiano](https://github.com/Ciro-Fusco/book_it) +- [Русский](https://github.com/rust-lang-ru/book) - [한국어](https://github.com/rinthel/rust-lang-book-ko) -- [日本語](https://github.com/hazama-yuinyan/book) -- [Français](https://github.com/quadrifoglio/rust-book-fr) +- [日本語](https://github.com/rust-lang-ja/book-ja) +- [Français](https://github.com/Jimskapt/rust-book-fr) - [Polski](https://github.com/paytchoo/book-pl) -- [עברית](https://github.com/idanmel/rust-book-heb) - [Cebuano](https://github.com/agentzero1/book) - [Tagalog](https://github.com/josephace135/book) - [Esperanto](https://github.com/psychoslave/Rust-libro) - [ελληνική](https://github.com/TChatzigiannakis/rust-book-greek) - [Svenska](https://github.com/sebras/book) - [Farsi](https://github.com/pomokhtari/rust-book-fa) +- [Deutsch](https://github.com/rust-lang-de/rustbook-de) +- [Turkish](https://github.com/RustDili/dokuman/tree/master/ceviriler), [online](https://rustdili.github.io/) +- [हिंदी](https://github.com/venkatarun95/rust-book-hindi) +- [ไทย](https://github.com/rust-lang-th/book-th) diff --git a/src/appendix-07-nightly-rust.md b/src/appendix-07-nightly-rust.md index 2bef848..2c36c34 100644 --- a/src/appendix-07-nightly-rust.md +++ b/src/appendix-07-nightly-rust.md @@ -1,8 +1,7 @@ ## 附录 G:Rust 是如何开发的与 “Nightly Rust” -> [appendix-07-nightly-rust.md](https://github.com/rust-lang/book/blob/main/src/appendix-07-nightly-rust.md) ->
-> commit 70a82519e48b8a61f98cabb8ff443d1b21962fea +> [appendix-07-nightly-rust.md](https://github.com/rust-lang/book/blob/main/src/appendix-07-nightly-rust.md) >
+> commit d44317c3122b44fb713aba66cc295dee3453b24b 本附录介绍 Rust 是如何开发的以及这如何影响作为 Rust 开发者的你。 @@ -14,11 +13,11 @@ ### Choo, Choo! ~~(开车啦,逃)~~ 发布通道和发布时刻表(Riding the Trains) -Rust 开发运行于一个 ~~车次表~~ **发布时刻表**(*train schedule*)之上。也就是说,所有的开发工作都位于 Rust 仓库的 `master` 分支。发布采用 software release train 模型,其被用于思科 IOS 等其它软件项目。Rust 有三个 **发布通道**(*release channel*): +Rust 开发运行于一个 ~~车次表~~ **发布时刻表**(_train schedule_)之上。也就是说,所有的开发工作都位于 Rust 仓库的 `master` 分支。发布采用 software release train 模型,其被用于思科 IOS 等其它软件项目。Rust 有三个 **发布通道**(_release channel_): -* Nightly -* Beta -* Stable(稳定版) +- Nightly +- Beta +- Stable(稳定版) 大部分 Rust 开发者主要采用稳定版通道,不过希望实验新功能的开发者可能会使用 nightly 或 beta 版。 @@ -90,11 +89,11 @@ Rust 每 6 周发布一个版本,如时钟般准确。如果你知道了某个 Rustup 使得改变不同发布通道的 Rust 更为简单,其在全局或分项目的层次工作。其默认会安装稳定版 Rust。例如为了安装 nightly: -```text -$ rustup install nightly +```console +$ rustup toolchain install nightly ``` -你会发现 `rustup` 也安装了所有的 **工具链**(*toolchains*, Rust 和其相关组件)。如下是一位作者的 Windows 计算机上的例子: +你会发现 `rustup` 也安装了所有的 **工具链**(_toolchains_, Rust 和其相关组件)。如下是一位作者的 Windows 计算机上的例子: ```powershell > rustup toolchain list @@ -105,12 +104,12 @@ nightly-x86_64-pc-windows-msvc 如你所见,默认是稳定版。大部分 Rust 用户在大部分时间使用稳定版。你可能也会这么做,不过如果你关心最新的功能,可以为特定项目使用 nightly 版。为此,可以在项目目录使用 `rustup override` 来设置当前目录 `rustup` 使用 nightly 工具链: -```text +```console $ cd ~/projects/needs-nightly $ rustup override set nightly ``` -现在,每次在 *~/projects/needs-nightly* 调用 `rustc` 或 `cargo`,`rustup` 会确保使用 nightly 版 Rust。在你有很多 Rust 项目时大有裨益! +现在,每次在 _~/projects/needs-nightly_ 调用 `rustc` 或 `cargo`,`rustup` 会确保使用 nightly 版 Rust。在你有很多 Rust 项目时大有裨益! ### RFC 过程和团队 @@ -118,6 +117,6 @@ $ rustup override set nightly 任何人都可以编写 RFC 来改进 Rust,同时这些 RFC 会被 Rust 团队评审和讨论,他们由很多不同分工的子团队组成。这里是 [Rust 官网上](https://www.rust-lang.org/governance) 所有团队的总列表,其包含了项目中每个领域的团队:语言设计、编译器实现、基础设施、文档等。各个团队会阅读相应的提议和评论,编写回复,并最终达成接受或回绝功能的一致。 -如果功能被接受了,在 Rust 仓库会打开一个 issue,人们就可以实现它。实现功能的人当然可能不是最初提议功能的人!当实现完成后,其会合并到 `master` 分支并位于一个功能开关(feature gate)之后,正如 [“不稳定功能”](#unstable-features) 部分所讨论的。 +如果功能被接受了,在 Rust 仓库会打开一个 issue,人们就可以实现它。实现功能的人当然可能不是最初提议功能的人!当实现完成后,其会合并到 `master` 分支并位于一个功能开关(feature gate)之后,正如 [“不稳定功能”](#不稳定功能) 部分所讨论的。 在稍后的某个时间,一旦使用 nightly 版的 Rust 团队能够尝试这个功能了,团队成员会讨论这个功能,它如何在 nightly 中工作,并决定是否应该进入稳定版。如果决定继续推进,功能开关会移除,然后这个功能就被认为是稳定的了!乘着“发布的列车”,最终在新的稳定版 Rust 中出现。 diff --git a/src/ch20-01-single-threaded.md b/src/ch20-01-single-threaded.md index 4418b79..6a991eb 100644 --- a/src/ch20-01-single-threaded.md +++ b/src/ch20-01-single-threaded.md @@ -2,7 +2,7 @@ > [ch20-01-single-threaded.md](https://github.com/rust-lang/book/blob/main/src/ch20-01-single-threaded.md) >
-> commit f617d58c1a88dd2912739a041fd4725d127bf9fb +> commit 9c0fa2714859738ff73cbbb829592e4c037d7e46 首先让我们创建一个可运行的单线程 web server,不过在开始之前,我们将快速了解一下构建 web server 所涉及到的协议。这些协议的细节超出了本书的范畴,不过一个简单的概括会提供我们所需的信息。 @@ -14,7 +14,7 @@ TCP 是一个底层协议,它描述了信息如何从一个 server 到另一 所以我们的 web server 所需做的第一件事便是能够监听 TCP 连接。标准库提供了 `std::net` 模块处理这些功能。让我们一如既往新建一个项目: -```text +```console $ cargo new hello Created binary (application) `hello` project $ cd hello @@ -25,17 +25,7 @@ $ cd hello 文件名: src/main.rs ```rust,no_run -use std::net::TcpListener; - -fn main() { - let listener = TcpListener::bind("127.0.0.1:7878").unwrap(); - - for stream in listener.incoming() { - let stream = stream.unwrap(); - - println!("Connection established!"); - } -} +{{#rustdoc_include ../listings/ch20-web-server/listing-20-01/src/main.rs}} ``` 示例 20-1: 监听传入的流并在接收到流时打印信息 @@ -72,27 +62,7 @@ Connection established! 文件名: src/main.rs ```rust,no_run -use std::io::prelude::*; -use std::net::TcpStream; -use std::net::TcpListener; - -fn main() { - let listener = TcpListener::bind("127.0.0.1:7878").unwrap(); - - for stream in listener.incoming() { - let stream = stream.unwrap(); - - handle_connection(stream); - } -} - -fn handle_connection(mut stream: TcpStream) { - let mut buffer = [0; 1024]; - - stream.read(&mut buffer).unwrap(); - - println!("Request: {}", String::from_utf8_lossy(&buffer[..])); -} +{{#rustdoc_include ../listings/ch20-web-server/listing-20-02/src/main.rs}} ``` 示例 20-2: 读取 `TcpStream` 并打印数据 @@ -107,10 +77,10 @@ fn handle_connection(mut stream: TcpStream) { 让我们试一试!启动程序并再次在浏览器中发起请求。注意浏览器中仍然会出现错误页面,不过终端中程序的输出现在看起来像这样: -```text +```console $ cargo run Compiling hello v0.1.0 (file:///projects/hello) - Finished dev [unoptimized + debuginfo] target(s) in 0.42 secs + Finished dev [unoptimized + debuginfo] target(s) in 0.42s Running `target/debug/hello` Request: GET / HTTP/1.1 Host: 127.0.0.1:7878 @@ -174,19 +144,8 @@ HTTP/1.1 200 OK\r\n\r\n 文件名: src/main.rs -```rust -# use std::io::prelude::*; -# use std::net::TcpStream; -fn handle_connection(mut stream: TcpStream) { - let mut buffer = [0; 1024]; - - stream.read(&mut buffer).unwrap(); - - let response = "HTTP/1.1 200 OK\r\n\r\n"; - - stream.write(response.as_bytes()).unwrap(); - stream.flush().unwrap(); -} +```rust,no_run +{{#rustdoc_include ../listings/ch20-web-server/listing-20-03/src/main.rs:here}} ``` 示例 20-3: 将一个微型成功 HTTP 响应写入流 @@ -204,17 +163,7 @@ fn handle_connection(mut stream: TcpStream) { 文件名: hello.html ```html - - - - - Hello! - - -

Hello!

-

Hi from Rust

- - +{{#include ../listings/ch20-web-server/listing-20-04/hello.html}} ``` 示例 20-4: 一个简单的 HTML 文件用来作为响应 @@ -223,27 +172,8 @@ fn handle_connection(mut stream: TcpStream) { 文件名: src/main.rs -```rust -# use std::io::prelude::*; -# use std::net::TcpStream; -use std::fs; -// --snip-- - -fn handle_connection(mut stream: TcpStream) { - let mut buffer = [0; 1024]; - stream.read(&mut buffer).unwrap(); - - let contents = fs::read_to_string("hello.html").unwrap(); - - let response = format!( - "HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n{}", - contents.len(), - contents - ); - - stream.write(response.as_bytes()).unwrap(); - stream.flush().unwrap(); -} +```rust,no_run +{{#rustdoc_include ../listings/ch20-web-server/listing-20-05/src/main.rs:here}} ``` 示例 20-5: 将 *hello.html* 的内容作为响应 body 发送 @@ -262,33 +192,8 @@ fn handle_connection(mut stream: TcpStream) { 文件名: src/main.rs -```rust -# use std::io::prelude::*; -# use std::net::TcpStream; -# use std::fs; -// --snip-- - -fn handle_connection(mut stream: TcpStream) { - let mut buffer = [0; 1024]; - stream.read(&mut buffer).unwrap(); - - let get = b"GET / HTTP/1.1\r\n"; - - if buffer.starts_with(get) { - let contents = fs::read_to_string("hello.html").unwrap(); - - let response = format!( - "HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n{}", - contents.len(), - contents - ); - - stream.write(response.as_bytes()).unwrap(); - stream.flush().unwrap(); - } else { - // 其他请求 - } -} +```rust,no_run +{{#rustdoc_include ../listings/ch20-web-server/listing-20-06/src/main.rs:here}} ``` 示例 20-6: 匹配请求并区别处理 */* 请求与其他请求 @@ -303,28 +208,8 @@ fn handle_connection(mut stream: TcpStream) { 文件名: src/main.rs -```rust -# use std::io::prelude::*; -# use std::net::TcpStream; -# use std::fs; -# fn handle_connection(mut stream: TcpStream) { -# if true { -// --snip-- - -} else { - let status_line = "HTTP/1.1 404 NOT FOUND\r\n\r\n"; - let contents = fs::read_to_string("404.html").unwrap(); - - let response = format!("{}\r\nContent-Length: {}\r\n\r\n{}", - status_line, - contents.len(), - contents - ); - - stream.write(response.as_bytes()).unwrap(); - stream.flush().unwrap(); -} -# } +```rust,no_run +{{#rustdoc_include ../listings/ch20-web-server/listing-20-07/src/main.rs:here}} ``` 示例 20-7: 对于任何不是 */* 的请求返回 `404` 状态码的响应和错误页面 @@ -334,17 +219,7 @@ fn handle_connection(mut stream: TcpStream) { 文件名: 404.html ```html - - - - - Hello! - - -

Oops!

-

Sorry, I don't know what you're asking for.

- - +{{#include ../listings/ch20-web-server/listing-20-08/404.html}} ``` 示例 20-8: 任何 404 响应所返回错误页面内容样例 @@ -357,36 +232,8 @@ fn handle_connection(mut stream: TcpStream) { 文件名: src/main.rs -```rust -# use std::io::prelude::*; -# use std::net::TcpStream; -# use std::fs; -// --snip-- - -fn handle_connection(mut stream: TcpStream) { -# let mut buffer = [0; 1024]; -# stream.read(&mut buffer).unwrap(); -# -# let get = b"GET / HTTP/1.1\r\n"; - // --snip-- - - let (status_line, filename) = if buffer.starts_with(get) { - ("HTTP/1.1 200 OK", "hello.html") - } else { - ("HTTP/1.1 404 NOT FOUND", "404.html") - }; - - let contents = fs::read_to_string(filename).unwrap(); - - let response = format!("{}\r\nContent-Length: {}\r\n\r\n{}", - status_line, - contents.len(), - contents - ); - - stream.write(response.as_bytes()).unwrap(); - stream.flush().unwrap(); -} +```rust,no_run +{{#rustdoc_include ../listings/ch20-web-server/listing-20-09/src/main.rs:here}} ``` 示例 20-9: 重构使得 `if` 和 `else` 块中只包含两个情况所不同的代码 diff --git a/src/ch20-02-multithreaded.md b/src/ch20-02-multithreaded.md index 6391bf5..588e895 100644 --- a/src/ch20-02-multithreaded.md +++ b/src/ch20-02-multithreaded.md @@ -2,7 +2,7 @@ > [ch20-02-multithreaded.md](https://github.com/rust-lang/book/blob/main/src/ch20-02-multithreaded.md) >
-> commit 120e76a0cc77c9cde52643f847ed777f8f441817 +> commit 95b5e7c86d33e98eec6f73b268d106621f3d24a1 目前 server 会依次处理每一个请求,意味着它在完成第一个连接的处理之前不会处理第二个连接。如果 server 正接收越来越多的请求,这类串行操作会使性能越来越差。如果一个请求花费很长时间来处理,随后而来的请求则不得不等待这个长请求结束,即便这些新请求可以很快就处理完。我们需要修复这种情况,不过首先让我们实际尝试一下这个问题。 @@ -12,33 +12,8 @@ 文件名: src/main.rs -```rust -use std::thread; -use std::time::Duration; -# use std::io::prelude::*; -# use std::net::TcpStream; -# use std::fs::File; -// --snip-- - -fn handle_connection(mut stream: TcpStream) { -# let mut buffer = [0; 1024]; -# stream.read(&mut buffer).unwrap(); - // --snip-- - - let get = b"GET / HTTP/1.1\r\n"; - let sleep = b"GET /sleep HTTP/1.1\r\n"; - - let (status_line, filename) = if buffer.starts_with(get) { - ("HTTP/1.1 200 OK", "hello.html") - } else if buffer.starts_with(sleep) { - thread::sleep(Duration::from_secs(5)); - ("HTTP/1.1 200 OK", "hello.html") - } else { - ("HTTP/1.1 404 NOT FOUND", "404.html") - }; - - // --snip-- -} +```rust,no_run +{{#rustdoc_include ../listings/ch20-web-server/listing-20-10/src/main.rs:here}} ``` 示例 20-10: 通过识别 */sleep* 并休眠五秒来模拟慢请求 @@ -72,23 +47,7 @@ fn handle_connection(mut stream: TcpStream) { 文件名: src/main.rs ```rust,no_run -# use std::thread; -# use std::io::prelude::*; -# use std::net::TcpListener; -# use std::net::TcpStream; -# -fn main() { - let listener = TcpListener::bind("127.0.0.1:7878").unwrap(); - - for stream in listener.incoming() { - let stream = stream.unwrap(); - - thread::spawn(|| { - handle_connection(stream); - }); - } -} -# fn handle_connection(mut stream: TcpStream) {} +{{#rustdoc_include ../listings/ch20-web-server/listing-20-11/src/main.rs:here}} ``` 示例 20-11: 为每一个流新建一个线程 @@ -101,31 +60,8 @@ fn main() { 文件名: src/main.rs -```rust,no_run -# use std::thread; -# use std::io::prelude::*; -# use std::net::TcpListener; -# use std::net::TcpStream; -# struct ThreadPool; -# impl ThreadPool { -# fn new(size: u32) -> ThreadPool { ThreadPool } -# fn execute(&self, f: F) -# where F: FnOnce() + Send + 'static {} -# } -# -fn main() { - let listener = TcpListener::bind("127.0.0.1:7878").unwrap(); - let pool = ThreadPool::new(4); - - for stream in listener.incoming() { - let stream = stream.unwrap(); - - pool.execute(|| { - handle_connection(stream); - }); - } -} -# fn handle_connection(mut stream: TcpStream) {} +```rust,ignore,does_not_compile +{{#rustdoc_include ../listings/ch20-web-server/listing-20-12/src/main.rs:here}} ``` 示例 20-12: 假想的 `ThreadPool` 接口 @@ -136,17 +72,8 @@ fn main() { 继续并对示例 20-12 中的 *src/main.rs* 做出修改,并利用来自 `cargo check` 的编译器错误来驱动开发。下面是我们得到的第一个错误: -```text -$ cargo check - Compiling hello v0.1.0 (file:///projects/hello) -error[E0433]: failed to resolve. Use of undeclared type or module `ThreadPool` - --> src\main.rs:10:16 - | -10 | let pool = ThreadPool::new(4); - | ^^^^^^^^^^^^^^^ Use of undeclared type or module - `ThreadPool` - -error: aborting due to previous error +```console +{{#include ../listings/ch20-web-server/listing-20-12/output.txt}} ``` 好的,这告诉我们需要一个 `ThreadPool` 类型或模块,所以我们将构建一个。`ThreadPool` 的实现会与 web server 的特定工作相独立,所以让我们从 `hello` crate 切换到存放 `ThreadPool` 实现的新库 crate。这也意味着可以在任何工作中使用这个单独的线程池库,而不仅仅是处理网络请求。 @@ -155,8 +82,8 @@ error: aborting due to previous error 文件名: src/lib.rs -```rust -pub struct ThreadPool; +```rust,noplayground +{{#rustdoc_include ../listings/ch20-web-server/no-listing-01-define-threadpool-struct/src/lib.rs}} ``` 接着创建一个新目录,*src/bin*,并将二进制 crate 根文件从 *src/main.rs* 移动到 *src/bin/main.rs*。这使得库 crate 成为 *hello* 目录的主要 crate;不过仍然可以使用 `cargo run` 运行 *src/bin/main.rs* 二进制文件。移动了 *main.rs* 文件之后,修改 *src/bin/main.rs* 文件开头加入如下代码来引入库 crate 并将 `ThreadPool` 引入作用域: @@ -164,58 +91,29 @@ pub struct ThreadPool; 文件名: src/bin/main.rs ```rust,ignore -use hello::ThreadPool; +{{#rustdoc_include ../listings/ch20-web-server/no-listing-01-define-threadpool-struct/src/bin/main.rs:here}} ``` 这仍然不能工作,再次尝试运行来得到下一个需要解决的错误: -```text -$ cargo check - Compiling hello v0.1.0 (file:///projects/hello) -error[E0599]: no function or associated item named `new` found for type -`hello::ThreadPool` in the current scope - --> src/bin/main.rs:13:16 - | -13 | let pool = ThreadPool::new(4); - | ^^^^^^^^^^^^^^^ function or associated item not found in - `hello::ThreadPool` +```console +{{#include ../listings/ch20-web-server/no-listing-01-define-threadpool-struct/output.txt}} ``` 这告诉我们下一步是为 `ThreadPool` 创建一个叫做 `new` 的关联函数。我们还知道 `new` 需要有一个参数可以接受 `4`,而且 `new` 应该返回 `ThreadPool` 实例。让我们实现拥有此特征的最小化 `new` 函数: 文件夹: src/lib.rs -```rust -pub struct ThreadPool; - -impl ThreadPool { - pub fn new(size: usize) -> ThreadPool { - ThreadPool - } -} +```rust,noplayground +{{#rustdoc_include ../listings/ch20-web-server/no-listing-02-impl-threadpool-new/src/lib.rs}} ``` -这里选择 `usize` 作为 `size` 参数的类型,因为我们知道为负的线程数没有意义。我们还知道将使用 4 作为线程集合的元素数量,这也就是使用 `usize` 类型的原因,如第三章 [“整数类型”][integer-types] 部分所讲。 +这里选择 `usize` 作为 `size` 参数的类型,因为我们知道为负的线程数没有意义。我们还知道将使用 4 作为线程集合的元素数量,这也就是使用 `usize` 类型的原因,如第三章 [“整型”][integer-types] 部分所讲。 再次编译检查这段代码: -```text -$ cargo check - Compiling hello v0.1.0 (file:///projects/hello) -warning: unused variable: `size` - --> src/lib.rs:4:16 - | -4 | pub fn new(size: usize) -> ThreadPool { - | ^^^^ - | - = note: #[warn(unused_variables)] on by default - = note: to avoid this warning, consider using `_size` instead - -error[E0599]: no method named `execute` found for type `hello::ThreadPool` in the current scope - --> src/bin/main.rs:18:14 - | -18 | pool.execute(|| { - | ^^^^^^^ +```console +{{#include ../listings/ch20-web-server/no-listing-02-impl-threadpool-new/output.txt}} ``` 现在有了一个警告和一个错误。暂时先忽略警告,发生错误是因为并没有 `ThreadPool` 上的 `execute` 方法。回忆 [“为有限数量的线程创建一个类似的接口”](#creating-a-similar-interface-for-a-finite-number-of-threads) 部分我们决定线程池应该有与 `thread::spawn` 类似的接口,同时我们将实现 `execute` 函数来获取传递的闭包并将其传递给池中的空闲线程执行。 @@ -225,8 +123,9 @@ error[E0599]: no method named `execute` found for type `hello::ThreadPool` in th ```rust,ignore pub fn spawn(f: F) -> JoinHandle where - F: FnOnce() -> T + Send + 'static, - T: Send + 'static + F: FnOnce() -> T, + F: Send + 'static, + T: Send + 'static, ``` `F` 是这里我们关心的参数;`T` 与返回值有关所以我们并不关心。考虑到 `spawn` 使用 `FnOnce` 作为 `F` 的 trait bound,这可能也是我们需要的,因为最终会将传递给 `execute` 的参数传给 `spawn`。因为处理请求的线程只会执行闭包一次,这也进一步确认了 `FnOnce` 是我们需要的 trait,这里符合 `FnOnce` 中 `Once` 的意思。 @@ -235,43 +134,16 @@ pub fn spawn(f: F) -> JoinHandle 文件名: src/lib.rs -```rust -# pub struct ThreadPool; -impl ThreadPool { - // --snip-- - - pub fn execute(&self, f: F) - where - F: FnOnce() + Send + 'static - { - - } -} +```rust,noplayground +{{#rustdoc_include ../listings/ch20-web-server/no-listing-03-define-execute/src/lib.rs:here}} ``` `FnOnce` trait 仍然需要之后的 `()`,因为这里的 `FnOnce` 代表一个没有参数也没有返回值的闭包。正如函数的定义,返回值类型可以从签名中省略,不过即便没有参数也需要括号。 这里再一次增加了 `execute` 方法的最小化实现:它没有做任何工作,只是尝试让代码能够编译。再次进行检查: -```text -$ cargo check - Compiling hello v0.1.0 (file:///projects/hello) -warning: unused variable: `size` - --> src/lib.rs:4:16 - | -4 | pub fn new(size: usize) -> ThreadPool { - | ^^^^ - | - = note: #[warn(unused_variables)] on by default - = note: to avoid this warning, consider using `_size` instead - -warning: unused variable: `f` - --> src/lib.rs:8:30 - | -8 | pub fn execute(&self, f: F) - | ^ - | - = note: to avoid this warning, consider using `_f` instead +```console +{{#include ../listings/ch20-web-server/no-listing-03-define-execute/output.txt}} ``` 现在就只有警告了!这意味着能够编译了!注意如果尝试 `cargo run` 运行程序并在浏览器中发起请求,仍会在浏览器中出现在本章开始时那样的错误。这个库实际上还没有调用传递给 `execute` 的闭包! @@ -284,24 +156,8 @@ warning: unused variable: `f` 文件名: src/lib.rs -```rust -# pub struct ThreadPool; -impl ThreadPool { - /// 创建线程池。 - /// - /// 线程池中线程的数量。 - /// - /// # Panics - /// - /// `new` 函数在 size 为 0 时会 panic。 - pub fn new(size: usize) -> ThreadPool { - assert!(size > 0); - - ThreadPool - } - - // --snip-- -} +```rust,noplayground +{{#rustdoc_include ../listings/ch20-web-server/listing-20-13/src/lib.rs:here}} ``` 示例 20-13: 实现 `ThreadPool::new` 在 `size` 为零时 panic @@ -321,8 +177,9 @@ pub fn new(size: usize) -> Result { ```rust,ignore pub fn spawn(f: F) -> JoinHandle where - F: FnOnce() -> T + Send + 'static, - T: Send + 'static + F: FnOnce() -> T, + F: Send + 'static, + T: Send + 'static, ``` `spawn` 返回 `JoinHandle`,其中 `T` 是闭包返回的类型。尝试使用 `JoinHandle` 来看看会发生什么。在我们的情况中,传递给线程池的闭包会处理连接并不返回任何值,所以 `T` 将会是单元类型 `()`。 @@ -332,30 +189,7 @@ pub fn spawn(f: F) -> JoinHandle 文件名: src/lib.rs ```rust,ignore,not_desired_behavior -use std::thread; - -pub struct ThreadPool { - threads: Vec>, -} - -impl ThreadPool { - // --snip-- - pub fn new(size: usize) -> ThreadPool { - assert!(size > 0); - - let mut threads = Vec::with_capacity(size); - - for _ in 0..size { - // create some threads and store them in the vector - } - - ThreadPool { - threads - } - } - - // --snip-- -} +{{#rustdoc_include ../listings/ch20-web-server/listing-20-14/src/lib.rs:here}} ``` 示例 20-14: 为 `ThreadPool` 创建一个 vector 来存放线程 @@ -387,46 +221,8 @@ impl ThreadPool { 文件名: src/lib.rs -```rust -use std::thread; - -pub struct ThreadPool { - workers: Vec, -} - -impl ThreadPool { - // --snip-- - pub fn new(size: usize) -> ThreadPool { - assert!(size > 0); - - let mut workers = Vec::with_capacity(size); - - for id in 0..size { - workers.push(Worker::new(id)); - } - - ThreadPool { - workers - } - } - // --snip-- -} - -struct Worker { - id: usize, - thread: thread::JoinHandle<()>, -} - -impl Worker { - fn new(id: usize) -> Worker { - let thread = thread::spawn(|| {}); - - Worker { - id, - thread, - } - } -} +```rust,noplayground +{{#rustdoc_include ../listings/ch20-web-server/listing-20-15/src/lib.rs:here}} ``` 示例 20-15: 修改 `ThreadPool` 存放 `Worker` 实例而不是直接存放线程 @@ -455,54 +251,8 @@ impl Worker { 文件名: src/lib.rs -```rust -# use std::thread; -// --snip-- -use std::sync::mpsc; - -pub struct ThreadPool { - workers: Vec, - sender: mpsc::Sender, -} - -struct Job; - -impl ThreadPool { - // --snip-- - pub fn new(size: usize) -> ThreadPool { - assert!(size > 0); - - let (sender, receiver) = mpsc::channel(); - - let mut workers = Vec::with_capacity(size); - - for id in 0..size { - workers.push(Worker::new(id)); - } - - ThreadPool { - workers, - sender, - } - } - // --snip-- -} -# -# struct Worker { -# id: usize, -# thread: thread::JoinHandle<()>, -# } -# -# impl Worker { -# fn new(id: usize) -> Worker { -# let thread = thread::spawn(|| {}); -# -# Worker { -# id, -# thread, -# } -# } -# } +```rust,noplayground +{{#rustdoc_include ../listings/ch20-web-server/listing-20-16/src/lib.rs:here}} ``` 示例 20-16: 修改 `ThreadPool` 来储存一个发送 `Job` 实例的通道发送端 @@ -514,41 +264,7 @@ impl ThreadPool { 文件名: src/lib.rs ```rust,ignore,does_not_compile -impl ThreadPool { - // --snip-- - pub fn new(size: usize) -> ThreadPool { - assert!(size > 0); - - let (sender, receiver) = mpsc::channel(); - - let mut workers = Vec::with_capacity(size); - - for id in 0..size { - workers.push(Worker::new(id, receiver)); - } - - ThreadPool { - workers, - sender, - } - } - // --snip-- -} - -// --snip-- - -impl Worker { - fn new(id: usize, receiver: mpsc::Receiver) -> Worker { - let thread = thread::spawn(|| { - receiver; - }); - - Worker { - id, - thread, - } - } -} +{{#rustdoc_include ../listings/ch20-web-server/listing-20-17/src/lib.rs:here}} ``` 示例 20-17: 将通道的接收端传递给 worker @@ -557,18 +273,8 @@ impl Worker { 如果尝试 check 代码,会得到这个错误: -```text -$ cargo check - Compiling hello v0.1.0 (file:///projects/hello) -error[E0382]: use of moved value: `receiver` - --> src/lib.rs:27:42 - | -27 | workers.push(Worker::new(id, receiver)); - | ^^^^^^^^ value moved here in - previous iteration of loop - | - = note: move occurs because `receiver` has type - `std::sync::mpsc::Receiver`, which does not implement the `Copy` trait +```console +{{#include ../listings/ch20-web-server/listing-20-17/output.txt}} ``` 这段代码尝试将 `receiver` 传递给多个 `Worker` 实例。这是不行的,回忆第十六章:Rust 所提供的通道实现是多 **生产者**,单 **消费者** 的。这意味着不能简单的克隆通道的消费端来解决问题。即便可以,那也不是我们希望使用的技术;我们希望通过在所有的 worker 中共享单一 `receiver`,在线程间分发任务。 @@ -579,61 +285,8 @@ error[E0382]: use of moved value: `receiver` 文件名: src/lib.rs -```rust -# use std::thread; -# use std::sync::mpsc; -use std::sync::Arc; -use std::sync::Mutex; -// --snip-- - -# pub struct ThreadPool { -# workers: Vec, -# sender: mpsc::Sender, -# } -# struct Job; -# -impl ThreadPool { - // --snip-- - pub fn new(size: usize) -> ThreadPool { - assert!(size > 0); - - let (sender, receiver) = mpsc::channel(); - - let receiver = Arc::new(Mutex::new(receiver)); - - let mut workers = Vec::with_capacity(size); - - for id in 0..size { - workers.push(Worker::new(id, Arc::clone(&receiver))); - } - - ThreadPool { - workers, - sender, - } - } - - // --snip-- -} - -# struct Worker { -# id: usize, -# thread: thread::JoinHandle<()>, -# } -# -impl Worker { - fn new(id: usize, receiver: Arc>>) -> Worker { - // --snip-- -# let thread = thread::spawn(|| { -# receiver; -# }); -# -# Worker { -# id, -# thread, -# } - } -} +```rust,noplayground +{{#rustdoc_include ../listings/ch20-web-server/listing-20-18/src/lib.rs:here}} ``` 示例 20-18: 使用 `Arc` 和 `Mutex` 在 worker 间共享通道的接收端 @@ -648,31 +301,8 @@ impl Worker { 文件名: src/lib.rs -```rust -// --snip-- -# pub struct ThreadPool { -# workers: Vec, -# sender: mpsc::Sender, -# } -# use std::sync::mpsc; -# struct Worker {} - -type Job = Box; - -impl ThreadPool { - // --snip-- - - pub fn execute(&self, f: F) - where - F: FnOnce() + Send + 'static - { - let job = Box::new(f); - - self.sender.send(job).unwrap(); - } -} - -// --snip-- +```rust,noplayground +{{#rustdoc_include ../listings/ch20-web-server/listing-20-19/src/lib.rs:here}} ``` 示例 20-19: 为存放每一个闭包的 `Box` 创建一个 `Job` 类型别名,接着在通道中发出任务 @@ -683,27 +313,8 @@ impl ThreadPool { 文件名: src/lib.rs -```rust,ignore,does_not_compile -// --snip-- - -impl Worker { - fn new(id: usize, receiver: Arc>>) -> Worker { - let thread = thread::spawn(move || { - loop { - let job = receiver.lock().unwrap().recv().unwrap(); - - println!("Worker {} got a job; executing.", id); - - job(); - } - }); - - Worker { - id, - thread, - } - } -} +```rust,noplayground +{{#rustdoc_include ../listings/ch20-web-server/listing-20-20/src/lib.rs:here}} ``` 示例 20-20: 在 worker 线程中接收并执行任务 @@ -714,37 +325,35 @@ impl Worker { 调用 `recv` 会阻塞当前线程,所以如果还没有任务,其会等待直到有可用的任务。`Mutex` 确保一次只有一个 `Worker` 线程尝试请求任务。 -通过这个技巧,线程池处于可以运行的状态了!执行 `cargo run` 并发起一些请求: +现在线程池处于可以运行的状态了!执行 `cargo run` 并发起一些请求: -```text +```console $ cargo run Compiling hello v0.1.0 (file:///projects/hello) -warning: field is never used: `workers` +warning: field is never read: `workers` --> src/lib.rs:7:5 | 7 | workers: Vec, | ^^^^^^^^^^^^^^^^^^^^ | - = note: #[warn(dead_code)] on by default + = note: `#[warn(dead_code)]` on by default -warning: field is never used: `id` - --> src/lib.rs:61:5 +warning: field is never read: `id` + --> src/lib.rs:48:5 | -61 | id: usize, +48 | id: usize, | ^^^^^^^^^ - | - = note: #[warn(dead_code)] on by default -warning: field is never used: `thread` - --> src/lib.rs:62:5 +warning: field is never read: `thread` + --> src/lib.rs:49:5 | -62 | thread: thread::JoinHandle<()>, +49 | thread: thread::JoinHandle<()>, | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: #[warn(dead_code)] on by default - Finished dev [unoptimized + debuginfo] target(s) in 0.99 secs - Running `target/debug/hello` +warning: 3 warnings emitted + + Finished dev [unoptimized + debuginfo] target(s) in 1.40s + Running `target/debug/main` Worker 0 got a job; executing. Worker 2 got a job; executing. Worker 1 got a job; executing. @@ -766,34 +375,17 @@ Worker 2 got a job; executing. 文件名: src/lib.rs ```rust,ignore,not_desired_behavior -// --snip-- - -impl Worker { - fn new(id: usize, receiver: Arc>>) -> Worker { - let thread = thread::spawn(move || { - while let Ok(job) = receiver.lock().unwrap().recv() { - println!("Worker {} got a job; executing.", id); - - job(); - } - }); - - Worker { - id, - thread, - } - } -} +{{#rustdoc_include ../listings/ch20-web-server/listing-20-21/src/lib.rs:here}} ``` 示例 20-21: 一个使用 `while let` 的 `Worker::new` 替代实现 -这段代码可以编译和运行,但是并不会产生所期望的线程行为:一个慢请求仍然会导致其他请求等待执行。其原因有些微妙:`Mutex` 结构体没有公有 `unlock` 方法,因为锁的所有权依赖 `lock` 方法返回的 `LockResult>` 中 `MutexGuard` 的生命周期。这允许借用检查器在编译时确保绝不会在没有持有锁的情况下访问由 `Mutex` 守护的资源,不过如果没有认真的思考 `MutexGuard` 的生命周期的话,也可能会导致比预期更久的持有锁。因为 `while` 表达式中的值在整个块一直处于作用域中,`job()` 调用的过程中其仍然持有锁,这意味着其他 worker 不能接收任务。 +这段代码可以编译和运行,但是并不会产生所期望的线程行为:一个慢请求仍然会导致其他请求等待执行。其原因有些微妙:`Mutex` 结构体没有公有 `unlock` 方法,因为锁的所有权依赖 `lock` 方法返回的 `LockResult>` 中 `MutexGuard` 的生命周期。这允许借用检查器在编译时确保绝不会在没有持有锁的情况下访问由 `Mutex` 守护的资源,不过如果没有认真的思考 `MutexGuard` 的生命周期的话,也可能会导致比预期更久的持有锁。 -相反通过使用 `loop` 并在循环块之内而不是之外获取锁和任务,`lock` 方法返回的 `MutexGuard` 在 `let job` 语句结束之后立刻就被丢弃了。这确保了 `recv` 调用过程中持有锁,而在 `job()` 调用前锁就被释放了,这就允许并发处理多个请求了。 +示例 20-20 中的代码使用的 `let job = receiver.lock().unwrap().recv().unwrap();` 之所以可以工作是因为对于 `let` 来说,当 `let` 语句结束时任何表达式中等号右侧使用的临时值都会立即被丢弃。然而 `while let`(`if let` 和 `match`)直到相关的代码块结束都不会丢弃临时值。在示例 20-21 中,`job()` 调用期间锁一直持续,这也意味着其他的 worker 无法接受任务。 [creating-type-synonyms-with-type-aliases]: -ch19-04-advanced-types.html#creating-type-synonyms-with-type-aliases -[integer-types]: ch03-02-data-types.html#integer-types +ch19-04-advanced-types.html#类型别名用来创建类型同义词 +[integer-types]: ch03-02-data-types.html#整型 [storing-closures-using-generic-parameters-and-the-fn-traits]: -ch13-01-closures.html#storing-closures-using-generic-parameters-and-the-fn-traits +ch13-01-closures.html#使用带有泛型和-fn-trait-的闭包 diff --git a/src/ch20-02-slow-requests.md b/src/ch20-02-slow-requests.md deleted file mode 100644 index 8e71f49..0000000 --- a/src/ch20-02-slow-requests.md +++ /dev/null @@ -1,66 +0,0 @@ -## 慢请求如何影响吞吐率 - -> [ch20-02-slow-requests.md](https://github.com/rust-lang/book/blob/main/second-edition/src/ch20-02-slow-requests.md) ->
-> commit d06a6a181fd61704cbf7feb55bc61d518c6469f9 - -目前 server 会依次处理每一个请求。这对于向我们这样并不期望有非常大量请求的服务来说是可行的,不过随着程序变得更复杂,这样的串行处理并不是最优的。 - -因为当前的程序顺序处理处理连接,在完成第一个连接的处理之前不会处理第二个连接。如果一个请求花费很长时间来处理,这段时间接收的请求则不得不等待这个长请求结束,即便这些新请求可以很快就处理完。让我们实际尝试一下。 - -### 在当前 server 实现中模拟慢请求 - -让我们看看一个花费很长时间处理的请求对当前的 server 实现有何影响。列表 20-10 展示了对另一个请求的响应代码,`/sleep`,它会使 server 在响应之前休眠五秒。这将模拟一个慢请求以便体现出 server 在串行的处理请求。 - -文件名: src/main.rs - -```rust -use std::thread; -use std::time::Duration; -# use std::io::prelude::*; -# use std::net::TcpStream; -# use std::fs::File; -// ...snip... - -fn handle_connection(mut stream: TcpStream) { -# let mut buffer = [0; 512]; -# stream.read(&mut buffer).unwrap(); - // ...snip... - - let get = b"GET / HTTP/1.1\r\n"; - let sleep = b"GET /sleep HTTP/1.1\r\n"; - - let (status_line, filename) = if buffer.starts_with(get) { - ("HTTP/1.1 200 OK\r\n\r\n", "hello.html") - } else if buffer.starts_with(sleep) { - thread::sleep(Duration::from_secs(5)); - ("HTTP/1.1 200 OK\r\n\r\n", "hello.html") - } else { - ("HTTP/1.1 404 NOT FOUND\r\n\r\n", "404.html") - }; - - // ...snip... -} -``` - -列表 20-10:通过识别 `/sleep` 并休眠五秒来模拟慢请求 - -这段代码有些凌乱,不过对于模拟的目的来说已经足够!这里创建了第二个请求 `sleep`,我们会识别其数据。在 `if` 块之后增加了一个 `else if` 来检查 `/sleep` 请求,当发现这个请求时,在渲染欢迎页面之前会先休眠五秒。 - -现在就可以真切的看出我们的 server 有多么的原始;真实的库将会以更简洁的方式处理多请求识别问题。 - -使用 `cargo run` 启动 server,并接着打开两个浏览器窗口:一个请求 `http://localhost:8080/` 而另一个请求 `http://localhost:8080/sleep`。如果像之前一样多次请求 `/`,会发现响应的比较快速。不过如果请求`/sleep` 之后再请求 `/`,就会看到 `/` 会等待直到 `sleep` 休眠完五秒之后才出现。 - -这里有多种办法来改变我们的 web server 使其避免所有请求都排在慢请求之后;其一便是实现一个线程池。 - -### 使用线程池改善吞吐量 - -**线程池**(*thread pool*)是一组预先分配的用来处理任务的线程。当程序收到一个新任务,线程池中的一个线程会被分配任务并开始处理。其余的线程则可用于处理在第一个线程处理任务的同时处理其他接收到的任务。当第一个线程处理完任务时,它会返回空闲线程池中等待处理新任务。 - -线程池允许我们并发处理连接:可以在老连接处理完之前就开始处理新连接。这增加了 server 的吞吐量。 - -如下是我们将要实现的:不再等待每个请求处理完才开始下一个,我们将每个连接的处理发送给不同的线程。这些线程来此程序启动时分配的四个线程的线程池。限制较少的线程数的原因是如果为每个新来的请求都创建一个新线程,则千万级的请求就造成灾难,他们会用尽服务器的资源并导致所有请求的处理都被终止。 - -不同于分配无限的线程,线程池中将有固定数量的等待线程。当新进请求时,将请求发送到线程池中做处理。线程池会维护一个接收请求的队列。每一个线程会从队列中取出一个请求,处理请求,接着向对队列索取另一个请求。通过这种设计,则可以并发处理 `N` 个请求,其中 `N` 为线程数。这仍然意味着 `N` 个慢请求会阻塞队列中的请求,不过确实将能够处理的慢请求数量从一增加到了 `N`。 - -这个设计是多种改善 web server 吞吐量的方法之一。不过本书并不是有关 web server 的,所以这一种方法是我们将要涉及的。其他的方法有 fork/join 模型和单线程异步 I/O 模型。如果你对这个主题感兴趣,则可以阅读更多关于其他解决方案的内容并尝试用 Rust 实现他们;对于一个像 Rust 这样的底层语言,所有这些方法都是可能的。 diff --git a/src/ch20-03-designing-the-interface.md b/src/ch20-03-designing-the-interface.md deleted file mode 100644 index db92442..0000000 --- a/src/ch20-03-designing-the-interface.md +++ /dev/null @@ -1,214 +0,0 @@ -## 设计线程池接口 - -> [ch20-03-designing-the-interface.md](https://github.com/rust-lang/book/blob/main/second-edition/src/ch20-03-designing-the-interface.md) ->
-> commit d06a6a181fd61704cbf7feb55bc61d518c6469f9 - -让我们讨论一下线程池看起来怎样。库作者们经常会发现,当尝试设计一些代码时,首先编写客户端接口确实有助于指导代码设计。以期望的调用方式来构建 API 代码的结构,接着在这个结构之内实现功能,而不是先实现功能再设计公有 API。 - -类似于第十二章项目中使用的测试驱动开发。这里将要使用编译器驱动开发(Compiler Driven Development)。我们将编写调用所期望的函数的代码,接着依靠编译器告诉我们接下来需要修改什么。编译器错误信息会指导我们的实现。 - -### 如果使用 `thread::spawn` 的代码结构 - -首先,让我们探索一下为每一个连接都创建一个线程看起来如何。这并不是最终方案,因为正如之前讲到的它会潜在的分配无限的线程,不过这是一个开始。列表 20-11 展示了 `main` 的改变,它在 `for` 循环中为每一个流分配了一个新线程进行处理: - -文件名: src/main.rs - -```rust,no_run -# use std::thread; -# use std::io::prelude::*; -# use std::net::TcpListener; -# use std::net::TcpStream; -# -fn main() { - let listener = TcpListener::bind("127.0.0.1:8080").unwrap(); - - for stream in listener.incoming() { - let stream = stream.unwrap(); - - thread::spawn(|| { - handle_connection(stream); - }); - } -} -# fn handle_connection(mut stream: TcpStream) {} -``` - -列表 20-11:为每一个流新建一个线程 - -正如第十六章讲到的,`thread::spawn` 会创建一个新线程并运行闭包中的代码。如果运行这段代码并在两个浏览器标签页中加载 `/sleep` 和 `/`,确实会发现 `/` 请求并没有等待 `/sleep` 结束。不过正如之前提到的,这最终会使系统崩溃因为我们无限制的创建新线程。 - -### 为 `ThreadPool` 创建一个类似的接口 - -我们期望线程池以类似且熟悉的方式工作,以便从线程切换到线程池并不会对运行于线程池中的代码做出较大的修改。列表 20-12 展示我们希望用来替换 `thread::spawn` 的 `ThreadPool` 结构体的假想接口: - -文件名: src/main.rs - -```rust,no_run -# use std::thread; -# use std::io::prelude::*; -# use std::net::TcpListener; -# use std::net::TcpStream; -# struct ThreadPool; -# impl ThreadPool { -# fn new(size: u32) -> ThreadPool { ThreadPool } -# fn execute(&self, f: F) -# where F: FnOnce() + Send + 'static {} -# } -# -fn main() { - let listener = TcpListener::bind("127.0.0.1:8080").unwrap(); - let pool = ThreadPool::new(4); - - for stream in listener.incoming() { - let stream = stream.unwrap(); - - pool.execute(|| { - handle_connection(stream); - }); - } -} -# fn handle_connection(mut stream: TcpStream) {} -``` - -列表 20-12:如何使用我们将要实现的 `ThreadPool` - -这里使用 `ThreadPool::new` 来创建一个新的线程池,它有一个可配置的线程数的参数,在这里是四。这样在 `for` 循环中,`pool.execute` 将会以类似 `thread::spawn` 的方式工作。 - -### 采用编译器驱动开发来驱动 API 的编译 - -继续并对列表 20-12 中的 *src/main.rs* 做出修改,并利用编译器错误来驱动开发。下面是我们得到的第一个错误: - -``` -$ cargo check - Compiling hello v0.1.0 (file:///projects/hello) -error[E0433]: failed to resolve. Use of undeclared type or module `ThreadPool` - --> src\main.rs:10:16 - | -10 | let pool = ThreadPool::new(4); - | ^^^^^^^^^^^^^^^ Use of undeclared type or module - `ThreadPool` - -error: aborting due to previous error -``` - -好的,我们需要一个 `ThreadPool`。将 `hello` crate 从二进制 crate 转换为库 crate 来存放 `ThreadPool` 实现,因为线程池实现与我们的 web server 的特定工作相独立。一旦写完了线程池库,就可以在任何工作中使用这个功能,而不仅仅是处理网络请求。 - -创建 *src/lib.rs* 文件,它包含了目前可用的最简单的 `ThreadPool` 定义: - -文件名: src/lib.rs - -```rust -pub struct ThreadPool; -``` - -接着创建一个新目录,*src/bin*,并将二进制 crate 根文件从 *src/main.rs* 移动到 *src/bin/main.rs*。这使得库 crate 成为 *hello* 目录的主要 crate;不过仍然可以使用 `cargo run` 运行 *src/bin/main.rs* 二进制文件。移动了 *main.rs* 文件之后,修改文件开头加入如下代码来引入库 crate 并将 `ThreadPool` 引入作用域: - -文件名: src/bin/main.rs - -```rust,ignore -extern crate hello; -use hello::ThreadPool; -``` - -再次尝试运行来得到下一个需要解决的错误: - -``` -$ cargo check - Compiling hello v0.1.0 (file:///projects/hello) -error: no associated item named `new` found for type `hello::ThreadPool` in the -current scope - --> src\main.rs:13:16 - | -13 | let pool = ThreadPool::new(4); - | ^^^^^^^^^^^^^^^ - | -``` - -好的,下一步是为 `ThreadPool` 创建一个叫做 `new` 的关联函数。我们还知道 `new` 需要有一个参数可以接受 `4`,而且 `new` 应该返回 `ThreadPool` 实例。让我们实现拥有此特征的最小化 `new` 函数: - -文件夹: src/lib.rs - -```rust -pub struct ThreadPool; - -impl ThreadPool { - pub fn new(size: u32) -> ThreadPool { - ThreadPool - } -} -``` - -这里的 `size` 参数是 `u32` 类型,因为我们知道为负的线程数没有意义。`u32` 是一个很好的默认值。一旦真正实现了 `new`,我们将考虑实现需要选择什么类型,目前我们仅仅处理编译器错误。 - -再次编译检查这段代码: - -``` -$ cargo check - Compiling hello v0.1.0 (file:///projects/hello) -warning: unused variable: `size`, #[warn(unused_variables)] on by default - --> src/lib.rs:4:16 - | -4 | pub fn new(size: u32) -> ThreadPool { - | ^^^^ - -error: no method named `execute` found for type `hello::ThreadPool` in the -current scope - --> src/main.rs:18:14 - | -18 | pool.execute(|| { - | ^^^^^^^ -``` - -好的,一个警告和一个错误。暂时先忽略警告,错误是因为并没有 `ThreadPool` 上的 `execute` 方法。让我们来定义一个,它应该能接受一个闭包。如果你还记得第十三章,闭包作为参数时可以使用三个不同的 trait:`Fn`、`FnMut` 和 `FnOnce`。那么应该用哪一种闭包呢?好吧,最终需要实现的类似于 `thread::spawn`;`thread::spawn` 的签名在其参数中使用了何种 bound 呢?查看文档会发现: - -```rust -pub fn spawn(f: F) -> JoinHandle - where - F: FnOnce() -> T + Send + 'static, - T: Send + 'static -``` - -`F` 是这里我们关心的参数;`T` 与返回值有关所以我们并不关心。考虑到 `spawn` 使用 `FnOnce` 作为 `F` 的 trait bound,这可能也是我们需要的,因为最终会将传递给 `execute` 的参数传给 `spawn`。因为处理请求的线程只会执行闭包一次,这也进一步确认了 `FnOnce` 是我们需要的 trait。 - -`F` 还有 trait bound `Send` 和生命周期绑定 `'static`,这对我们的情况也是有意义的:需要 `Send` 来将闭包从一个线程转移到另一个线程,而 `'static` 是因为并不知道线程会执行多久。让我们编写一个使用这些 bound 的泛型参数 `F` 的 `ThreadPool` 的 `execute` 方法: - -文件名: src/lib.rs - -```rust -# pub struct ThreadPool; -impl ThreadPool { - // ...snip... - - pub fn execute(&self, f: F) - where - F: FnOnce() + Send + 'static - { - - } -} -``` - -`FnOnce` trait 仍然需要之后的 `()`,因为这里的 `FnOnce` 代表一个没有参数也没有返回值的闭包。正如函数的定义,返回值类型可以从签名中省略,不过即便没有参数也需要括号。 - -因为我们仍在努力使接口能够编译,这里增加了 `execute` 方法的最小化实现,它没有做任何工作。再次进行检查: - -``` -$ cargo check - Compiling hello v0.1.0 (file:///projects/hello) -warning: unused variable: `size`, #[warn(unused_variables)] on by default - --> src/lib.rs:4:16 - | -4 | pub fn new(size: u32) -> ThreadPool { - | ^^^^ - -warning: unused variable: `f`, #[warn(unused_variables)] on by default - --> src/lib.rs:8:30 - | -8 | pub fn execute(&self, f: F) - | ^ -``` - -现在就只有警告了!能够编译了!注意如果尝试 `cargo run` 运行程序并在浏览器中发起请求,仍会在浏览器中出现在本章开始时那样的错误。这个库实际上还没有调用传递给 `execute` 的闭包! - -> 一个你可能听说过的关于像 Haskell 和 Rust 这样有严格编译器的语言的说法是“如果代码能够编译,它就能工作”。这是一个提醒大家的好时机,这只是一个说法和一种有时存在的感觉,实际上并不是完全正确的。我们的项目可以编译,不过它绝对没有做任何工作!如果构建一个真实且功能完整的项目,则需花费大量的时间来开始编写单元测试来检查代码能否编译**并且**拥有期望的行为。 diff --git a/src/ch20-03-graceful-shutdown-and-cleanup.md b/src/ch20-03-graceful-shutdown-and-cleanup.md index 332d3a4..6530cbb 100644 --- a/src/ch20-03-graceful-shutdown-and-cleanup.md +++ b/src/ch20-03-graceful-shutdown-and-cleanup.md @@ -2,42 +2,30 @@ > [ch20-03-graceful-shutdown-and-cleanup.md](https://github.com/rust-lang/book/blob/main/src/ch20-03-graceful-shutdown-and-cleanup.md) >
-> commit 9a5a1bfaec3b7763e1bcfd31a2fb19fe95183746 +> commit 322899b375d071e4d96aaf29ce25c1a4b4ec65da -示例 20-21 中的代码如期通过使用线程池异步的响应请求。这里有一些警告说 `workers`、`id` 和 `thread` 字段没有直接被使用,这提醒了我们并没有清理所有的内容。当使用不那么优雅的 ctrl-c 终止主线程时,所有其他线程也会立刻停止,即便它们正处于处理请求的过程中。 +示例 20-20 中的代码如期通过使用线程池异步的响应请求。这里有一些警告说 `workers`、`id` 和 `thread` 字段没有直接被使用,这提醒了我们并没有清理所有的内容。当使用不那么优雅的 ctrl-c 终止主线程时,所有其他线程也会立刻停止,即便它们正处于处理请求的过程中。 现在我们要为 `ThreadPool` 实现 `Drop` trait 对线程池中的每一个线程调用 `join`,这样这些线程将会执行完他们的请求。接着会为 `ThreadPool` 实现一个告诉线程他们应该停止接收新请求并结束的方式。为了实践这些代码,修改 server 在优雅停机(graceful shutdown)之前只接受两个请求。 ### 为 `ThreadPool` 实现 `Drop` Trait -现在开始为线程池实现 `Drop`。当线程池被丢弃时,应该 join 所有线程以确保他们完成其操作。示例 20-23 展示了 `Drop` 实现的第一次尝试;这些代码还不能够编译: +现在开始为线程池实现 `Drop`。当线程池被丢弃时,应该 join 所有线程以确保他们完成其操作。示例 20-22 展示了 `Drop` 实现的第一次尝试;这些代码还不能够编译: 文件名: src/lib.rs ```rust,ignore,does_not_compile -impl Drop for ThreadPool { - fn drop(&mut self) { - for worker in &mut self.workers { - println!("Shutting down worker {}", worker.id); - - worker.thread.join().unwrap(); - } - } -} +{{#rustdoc_include ../listings/ch20-web-server/listing-20-22/src/lib.rs:here}} ``` -示例 20-23: 当线程池离开作用域时 join 每个线程 +示例 20-22: 当线程池离开作用域时 join 每个线程 这里首先遍历线程池中的每个 `workers`。这里使用了 `&mut` 因为 `self` 本身是一个可变引用而且也需要能够修改 `worker`。对于每一个线程,会打印出说明信息表明此特定 worker 正在关闭,接着在 worker 线程上调用 `join`。如果 `join` 调用失败,通过 `unwrap` 使得 panic 并进行不优雅的关闭。 如下是尝试编译代码时得到的错误: -```text -error[E0507]: cannot move out of borrowed content - --> src/lib.rs:65:13 - | -65 | worker.thread.join().unwrap(); - | ^^^^^^ cannot move out of borrowed content +```console +{{#include ../listings/ch20-web-server/listing-20-22/output.txt}} ``` 这告诉我们并不能调用 `join`,因为只有每一个 `worker` 的可变借用,而 `join` 获取其参数的所有权。为了解决这个问题,需要一个方法将 `thread` 移动出拥有其所有权的 `Worker` 实例以便 `join` 可以消费这个线程。示例 17-15 中我们曾见过这么做的方法:如果 `Worker` 存放的是 `Option`,就可以在 `Option` 上调用 `take` 方法将值从 `Some` 成员中移动出来而对 `None` 成员不做处理。换句话说,正在运行的 `Worker` 的 `thread` 将是 `Some` 成员值,而当需要清理 worker 时,将 `Some` 替换为 `None`,这样 worker 就没有可以运行的线程了。 @@ -46,36 +34,14 @@ error[E0507]: cannot move out of borrowed content 文件名: src/lib.rs -```rust -# use std::thread; -struct Worker { - id: usize, - thread: Option>, -} +```rust,ignore,does_not_compile +{{#rustdoc_include ../listings/ch20-web-server/no-listing-04-update-worker-definition/src/lib.rs:here}} ``` 现在依靠编译器来找出其他需要修改的地方。check 代码会得到两个错误: -```text -error[E0599]: no method named `join` found for type -`std::option::Option>` in the current scope - --> src/lib.rs:65:27 - | -65 | worker.thread.join().unwrap(); - | ^^^^ - -error[E0308]: mismatched types - --> src/lib.rs:89:13 - | -89 | thread, - | ^^^^^^ - | | - | expected enum `std::option::Option`, found struct - `std::thread::JoinHandle` - | help: try using a variant of the expected type: `Some(thread)` - | - = note: expected type `std::option::Option>` - found type `std::thread::JoinHandle<_>` +```console +{{#include ../listings/ch20-web-server/no-listing-04-update-worker-definition/output.txt}} ``` 让我们修复第二个错误,它指向 `Worker::new` 结尾的代码;当新建 `Worker` 时需要将 `thread` 值封装进 `Some`。做出如下改变以修复问题: @@ -83,16 +49,7 @@ error[E0308]: mismatched types 文件名: src/lib.rs ```rust,ignore -impl Worker { - fn new(id: usize, receiver: Arc>>) -> Worker { - // --snip-- - - Worker { - id, - thread: Some(thread), - } - } -} +{{#rustdoc_include ../listings/ch20-web-server/no-listing-05-fix-worker-new/src/lib.rs:here}} ``` 第一个错误位于 `Drop` 实现中。之前提到过要调用 `Option` 上的 `take` 将 `thread` 移动出 `worker`。如下改变会修复问题: @@ -100,17 +57,7 @@ impl Worker { 文件名: src/lib.rs ```rust,ignore -impl Drop for ThreadPool { - fn drop(&mut self) { - for worker in &mut self.workers { - println!("Shutting down worker {}", worker.id); - - if let Some(thread) = worker.thread.take() { - thread.join().unwrap(); - } - } - } -} +{{#rustdoc_include ../listings/ch20-web-server/no-listing-06-fix-threadpool-drop/src/lib.rs:here}} ``` 如第十七章我们见过的,`Option` 上的 `take` 方法会取出 `Some` 而留下 `None`。使用 `if let` 解构 `Some` 并得到线程,接着在线程上调用 `join`。如果 worker 的线程已然是 `None`,就知道此时这个 worker 已经清理了其线程所以无需做任何操作。 @@ -123,105 +70,33 @@ impl Drop for ThreadPool { 文件名: src/lib.rs -```rust -# struct Job; -enum Message { - NewJob(Job), - Terminate, -} +```rust,noplayground +{{#rustdoc_include ../listings/ch20-web-server/no-listing-07-define-message-enum/src/lib.rs:here}} ``` `Message` 枚举要么是存放了线程需要运行的 `Job` 的 `NewJob` 成员,要么是会导致线程退出循环并终止的 `Terminate` 成员。 -同时需要修改通道来使用 `Message` 类型值而不是 `Job`,如示例 20-24 所示: +同时需要修改通道来使用 `Message` 类型值而不是 `Job`,如示例 20-23 所示: 文件名: src/lib.rs ```rust,ignore -pub struct ThreadPool { - workers: Vec, - sender: mpsc::Sender, -} - -// --snip-- - -impl ThreadPool { - // --snip-- - - pub fn execute(&self, f: F) - where - F: FnOnce() + Send + 'static - { - let job = Box::new(f); - - self.sender.send(Message::NewJob(job)).unwrap(); - } -} - -// --snip-- - -impl Worker { - fn new(id: usize, receiver: Arc>>) -> - Worker { - - let thread = thread::spawn(move ||{ - loop { - let message = receiver.lock().unwrap().recv().unwrap(); - - match message { - Message::NewJob(job) => { - println!("Worker {} got a job; executing.", id); - - job(); - }, - Message::Terminate => { - println!("Worker {} was told to terminate.", id); - - break; - }, - } - } - }); - - Worker { - id, - thread: Some(thread), - } - } -} +{{#rustdoc_include ../listings/ch20-web-server/listing-20-23/src/lib.rs:here}} ``` -示例 20-24: 收发 `Message` 值并在 `Worker` 收到 `Message::Terminate` 时退出循环 +示例 20-23: 收发 `Message` 值并在 `Worker` 收到 `Message::Terminate` 时退出循环 为了适用 `Message` 枚举需要将两个地方的 `Job` 修改为 `Message`:`ThreadPool` 的定义和 `Worker::new` 的签名。`ThreadPool` 的 `execute` 方法需要发送封装进 `Message::NewJob` 成员的任务。然后,在 `Worker::new` 中当从通道接收 `Message` 时,当获取到 `NewJob`成员会处理任务而收到 `Terminate` 成员则会退出循环。 -通过这些修改,代码再次能够编译并继续按照示例 20-21 之后相同的行为运行。不过还是会得到一个警告,因为并没有创建任何 `Terminate` 成员的消息。如示例 20-25 所示修改 `Drop` 实现来修复此问题: +通过这些修改,代码再次能够编译并继续按照示例 20-20 之后相同的行为运行。不过还是会得到一个警告,因为并没有创建任何 `Terminate` 成员的消息。如示例 20-24 所示修改 `Drop` 实现来修复此问题: 文件名: src/lib.rs ```rust,ignore -impl Drop for ThreadPool { - fn drop(&mut self) { - println!("Sending terminate message to all workers."); - - for _ in &mut self.workers { - self.sender.send(Message::Terminate).unwrap(); - } - - println!("Shutting down all workers."); - - for worker in &mut self.workers { - println!("Shutting down worker {}", worker.id); - - if let Some(thread) = worker.thread.take() { - thread.join().unwrap(); - } - } - } -} +{{#rustdoc_include ../listings/ch20-web-server/listing-20-24/src/lib.rs:here}} ``` -示例 20-25:在对每个 worker 线程调用 `join` 之前向 worker 发送 `Message::Terminate` +示例 20-24:在对每个 worker 线程调用 `join` 之前向 worker 发送 `Message::Terminate` 现在遍历了 worker 两次,一次向每个 worker 发送一个 `Terminate` 消息,一个调用每个 worker 线程上的 `join`。如果尝试在同一循环中发送消息并立即 join 线程,则无法保证当前迭代的 worker 是从通道收到终止消息的 worker。 @@ -229,28 +104,15 @@ impl Drop for ThreadPool { 为了避免此情况,首先在一个循环中向通道发出所有的 `Terminate` 消息,接着在另一个循环中 join 所有的线程。每个 worker 一旦收到终止消息即会停止从通道接收消息,意味着可以确保如果发送同 worker 数相同的终止消息,在 join 之前每个线程都会收到一个终止消息。 -为了实践这些代码,如示例 20-26 所示修改 `main` 在优雅停机 server 之前只接受两个请求: +为了实践这些代码,如示例 20-25 所示修改 `main` 在优雅停机 server 之前只接受两个请求: 文件名: src/bin/main.rs ```rust,ignore -fn main() { - let listener = TcpListener::bind("127.0.0.1:7878").unwrap(); - let pool = ThreadPool::new(4); - - for stream in listener.incoming().take(2) { - let stream = stream.unwrap(); - - pool.execute(|| { - handle_connection(stream); - }); - } - - println!("Shutting down."); -} +{{#rustdoc_include ../listings/ch20-web-server/listing-20-25/src/bin/main.rs:here}} ``` -示例 20-26: 在处理两个请求之后通过退出循环来停止 server +示例 20-25: 在处理两个请求之后通过退出循环来停止 server 你不会希望真实世界的 web server 只处理两次请求就停机了,这只是为了展示优雅停机和清理处于正常工作状态。 @@ -258,11 +120,11 @@ fn main() { 使用 `cargo run` 启动 server,并发起三个请求。第三个请求应该会失败,而终端的输出应该看起来像这样: -```text +```console $ cargo run Compiling hello v0.1.0 (file:///projects/hello) - Finished dev [unoptimized + debuginfo] target(s) in 1.0 secs - Running `target/debug/hello` + Finished dev [unoptimized + debuginfo] target(s) in 1.0s + Running `target/debug/main` Worker 0 got a job; executing. Worker 3 got a job; executing. Shutting down. @@ -289,175 +151,22 @@ Shutting down worker 3 文件名: src/bin/main.rs ```rust,ignore -use hello::ThreadPool; - -use std::io::prelude::*; -use std::net::TcpListener; -use std::net::TcpStream; -use std::fs; -use std::thread; -use std::time::Duration; - -fn main() { - let listener = TcpListener::bind("127.0.0.1:7878").unwrap(); - let pool = ThreadPool::new(4); - - for stream in listener.incoming().take(2) { - let stream = stream.unwrap(); - - pool.execute(|| { - handle_connection(stream); - }); - } - - println!("Shutting down."); -} - -fn handle_connection(mut stream: TcpStream) { - let mut buffer = [0; 1024]; - stream.read(&mut buffer).unwrap(); - - let get = b"GET / HTTP/1.1\r\n"; - let sleep = b"GET /sleep HTTP/1.1\r\n"; - - let (status_line, filename) = if buffer.starts_with(get) { - ("HTTP/1.1 200 OK\r\n\r\n", "hello.html") - } else if buffer.starts_with(sleep) { - thread::sleep(Duration::from_secs(5)); - ("HTTP/1.1 200 OK\r\n\r\n", "hello.html") - } else { - ("HTTP/1.1 404 NOT FOUND\r\n\r\n", "404.html") - }; - - let contents = fs::read_to_string(filename).unwrap(); - - let response = format!("{}{}", status_line, contents); - - stream.write(response.as_bytes()).unwrap(); - stream.flush().unwrap(); -} +{{#rustdoc_include ../listings/ch20-web-server/no-listing-08-final-code/src/bin/main.rs}} ``` 文件名: src/lib.rs -```rust -use std::thread; -use std::sync::mpsc; -use std::sync::Arc; -use std::sync::Mutex; - -enum Message { - NewJob(Job), - Terminate, -} - -pub struct ThreadPool { - workers: Vec, - sender: mpsc::Sender, -} - -type Job = Box; - -impl ThreadPool { - /// 创建线程池。 - /// - /// 线程池中线程的数量。 - /// - /// # Panics - /// - /// `new` 函数在 size 为 0 时会 panic。 - pub fn new(size: usize) -> ThreadPool { - assert!(size > 0); - - let (sender, receiver) = mpsc::channel(); - - let receiver = Arc::new(Mutex::new(receiver)); - - let mut workers = Vec::with_capacity(size); - - for id in 0..size { - workers.push(Worker::new(id, Arc::clone(&receiver))); - } - - ThreadPool { - workers, - sender, - } - } - - pub fn execute(&self, f: F) - where - F: FnOnce() + Send + 'static - { - let job = Box::new(f); - - self.sender.send(Message::NewJob(job)).unwrap(); - } -} - -impl Drop for ThreadPool { - fn drop(&mut self) { - println!("Sending terminate message to all workers."); - - for _ in &mut self.workers { - self.sender.send(Message::Terminate).unwrap(); - } - - println!("Shutting down all workers."); - - for worker in &mut self.workers { - println!("Shutting down worker {}", worker.id); - - if let Some(thread) = worker.thread.take() { - thread.join().unwrap(); - } - } - } -} - -struct Worker { - id: usize, - thread: Option>, -} - -impl Worker { - fn new(id: usize, receiver: Arc>>) -> - Worker { - - let thread = thread::spawn(move ||{ - loop { - let message = receiver.lock().unwrap().recv().unwrap(); - - match message { - Message::NewJob(job) => { - println!("Worker {} got a job; executing.", id); - - job(); - }, - Message::Terminate => { - println!("Worker {} was told to terminate.", id); - - break; - }, - } - } - }); - - Worker { - id, - thread: Some(thread), - } - } -} +```rust,noplayground +{{#rustdoc_include ../listings/ch20-web-server/no-listing-08-final-code/src/lib.rs}} ``` 这里还有很多可以做的事!如果你希望继续增强这个项目,如下是一些点子: -- 为 `ThreadPool` 和其公有方法增加更多文档 -- 为库的功能增加测试 -- 将 `unwrap` 调用改为更健壮的错误处理 -- 使用 `ThreadPool` 进行其他不同于处理网络请求的任务 -- 在 [crates.io](https://crates.io/) 上寻找一个线程池 crate 并使用它实现一个类似的 web server,将其 API 和鲁棒性与我们的实现做对比 +* 为 `ThreadPool` 和其公有方法增加更多文档 +* 为库的功能增加测试 +* 将 `unwrap` 调用改为更健壮的错误处理 +* 使用 `ThreadPool` 进行其他不同于处理网络请求的任务 +* 在 [crates.io](https://crates.io/) 上寻找一个线程池 crate 并使用它实现一个类似的 web server,将其 API 和鲁棒性与我们的实现做对比 ## 总结