Merge branch 'master' into master

pull/36/head
Murphy 8 years ago committed by GitHub
commit f6ef38716e

@ -79,7 +79,7 @@ commit 4f2dc564851dc04b271a2260c834643dfd86c724</p>
<ul> <ul>
<li><code>Box&lt;T&gt;</code>,用于在堆上分配值</li> <li><code>Box&lt;T&gt;</code>,用于在堆上分配值</li>
<li><code>Rc&lt;T&gt;</code>,一个引用计数类型,其数据可以有多个所有者</li> <li><code>Rc&lt;T&gt;</code>,一个引用计数类型,其数据可以有多个所有者</li>
<li><code>RefCell&lt;T&gt;</code>,其本身并不是能指针,不过它管理智能指针<code>Ref</code><code>RefMut</code>的访问,在运行时而不是在编译时执行借用规则。</li> <li><code>RefCell&lt;T&gt;</code>,其本身并不是能指针,不过它管理智能指针<code>Ref</code><code>RefMut</code>的访问,在运行时而不是在编译时执行借用规则。</li>
</ul> </ul>
<p>同时我们还将涉及:</p> <p>同时我们还将涉及:</p>
<ul> <ul>

@ -147,7 +147,7 @@ struct that holds mp3 file data and metadata</span></p>
<li><code>&amp;mut T</code><code>&amp;U</code><code>T: Deref&lt;Target=U&gt;</code></li> <li><code>&amp;mut T</code><code>&amp;U</code><code>T: Deref&lt;Target=U&gt;</code></li>
</ul> </ul>
<p>头两个情况除了可变性之外是相同的:如果有一个<code>&amp;T</code>,而<code>T</code>实现了返回<code>U</code>类型的<code>Deref</code>,可以直接得到<code>&amp;U</code>。对于可变引用也是一样。最后一个有些微妙如果有一个可变引用它也可以强转为一个不可变引用。反之则是_不可能_的不可变引用永远也不能强转为可变引用。</p> <p>头两个情况除了可变性之外是相同的:如果有一个<code>&amp;T</code>,而<code>T</code>实现了返回<code>U</code>类型的<code>Deref</code>,可以直接得到<code>&amp;U</code>。对于可变引用也是一样。最后一个有些微妙如果有一个可变引用它也可以强转为一个不可变引用。反之则是_不可能_的不可变引用永远也不能强转为可变引用。</p>
<p><code>Deref</code> trait 对于能指针模式十分重要的原因在于智能指针可以被看作普通引用并用于期望使用引用的地方。例如,无需重新编写方法和函数来直接获取智能指针。</p> <p><code>Deref</code> trait 对于能指针模式十分重要的原因在于智能指针可以被看作普通引用并用于期望使用引用的地方。例如,无需重新编写方法和函数来直接获取智能指针。</p>
</div> </div>

@ -8260,7 +8260,7 @@ commit 4f2dc564851dc04b271a2260c834643dfd86c724</p>
<ul> <ul>
<li><code>Box&lt;T&gt;</code>,用于在堆上分配值</li> <li><code>Box&lt;T&gt;</code>,用于在堆上分配值</li>
<li><code>Rc&lt;T&gt;</code>,一个引用计数类型,其数据可以有多个所有者</li> <li><code>Rc&lt;T&gt;</code>,一个引用计数类型,其数据可以有多个所有者</li>
<li><code>RefCell&lt;T&gt;</code>,其本身并不是能指针,不过它管理智能指针<code>Ref</code><code>RefMut</code>的访问,在运行时而不是在编译时执行借用规则。</li> <li><code>RefCell&lt;T&gt;</code>,其本身并不是能指针,不过它管理智能指针<code>Ref</code><code>RefMut</code>的访问,在运行时而不是在编译时执行借用规则。</li>
</ul> </ul>
<p>同时我们还将涉及:</p> <p>同时我们还将涉及:</p>
<ul> <ul>
@ -8446,7 +8446,7 @@ struct that holds mp3 file data and metadata</span></p>
<li><code>&amp;mut T</code><code>&amp;U</code><code>T: Deref&lt;Target=U&gt;</code></li> <li><code>&amp;mut T</code><code>&amp;U</code><code>T: Deref&lt;Target=U&gt;</code></li>
</ul> </ul>
<p>头两个情况除了可变性之外是相同的:如果有一个<code>&amp;T</code>,而<code>T</code>实现了返回<code>U</code>类型的<code>Deref</code>,可以直接得到<code>&amp;U</code>。对于可变引用也是一样。最后一个有些微妙如果有一个可变引用它也可以强转为一个不可变引用。反之则是_不可能_的不可变引用永远也不能强转为可变引用。</p> <p>头两个情况除了可变性之外是相同的:如果有一个<code>&amp;T</code>,而<code>T</code>实现了返回<code>U</code>类型的<code>Deref</code>,可以直接得到<code>&amp;U</code>。对于可变引用也是一样。最后一个有些微妙如果有一个可变引用它也可以强转为一个不可变引用。反之则是_不可能_的不可变引用永远也不能强转为可变引用。</p>
<p><code>Deref</code> trait 对于能指针模式十分重要的原因在于智能指针可以被看作普通引用并用于期望使用引用的地方。例如,无需重新编写方法和函数来直接获取智能指针。</p> <p><code>Deref</code> trait 对于能指针模式十分重要的原因在于智能指针可以被看作普通引用并用于期望使用引用的地方。例如,无需重新编写方法和函数来直接获取智能指针。</p>
<a class="header" href="#drop-trait-运行清理代码" name="drop-trait-运行清理代码"><h2><code>Drop</code> Trait 运行清理代码</h2></a> <a class="header" href="#drop-trait-运行清理代码" name="drop-trait-运行清理代码"><h2><code>Drop</code> Trait 运行清理代码</h2></a>
<blockquote> <blockquote>
<p><a href="https://github.com/rust-lang/book/blob/master/second-edition/src/ch15-03-drop.md">ch15-03-drop.md</a> <p><a href="https://github.com/rust-lang/book/blob/master/second-edition/src/ch15-03-drop.md">ch15-03-drop.md</a>

@ -118,7 +118,7 @@ fn value_in_cents(coin: Coin) -> i32 {
} }
``` ```
如果调用`value_in_cents(Coin::Quarter(UsState::Alaska))``coin`将是`Coin::Quarter(UsState::Alaska)`。当将值与每个分支相比较时,没有分支会匹配知道遇到`Coin::Quarter(state)`。这时,`state`绑定的将会是值`UsState::Alaska`。接着就可以在`println!`表达式中使用这个绑定了,像这样就可以获取`Coin`枚举的`Quarter`成员中内部的州的值。 如果调用`value_in_cents(Coin::Quarter(UsState::Alaska))``coin`将是`Coin::Quarter(UsState::Alaska)`。当将值与每个分支相比较时,没有分支会匹配直到遇到`Coin::Quarter(state)`。这时,`state`绑定的将会是值`UsState::Alaska`。接着就可以在`println!`表达式中使用这个绑定了,像这样就可以获取`Coin`枚举的`Quarter`成员中内部的州的值。
### 匹配`Option<T>` ### 匹配`Option<T>`

@ -30,7 +30,7 @@ if let Some(3) = some_u8_value {
`if let`获取通过`=`分隔的一个模式和一个表达式。它的工作方式与`match`相同,这里的表达式对应`match`而模式则对应第一个分支。 `if let`获取通过`=`分隔的一个模式和一个表达式。它的工作方式与`match`相同,这里的表达式对应`match`而模式则对应第一个分支。
使用`if let`意味着编写更少代码,更少的缩进和更少的样板代码。然而,这样会失去`match`强制要求的穷进行检查。`match`和`if let`之间的选择依赖特定的环境以及增加简洁度和失去穷尽性检查的权衡取舍。 使用`if let`意味着编写更少代码,更少的缩进和更少的样板代码。然而,这样会失去`match`强制要求的穷尽性检查。`match`和`if let`之间的选择依赖特定的环境以及增加简洁度和失去穷尽性检查的权衡取舍。
换句话说,可以认为`if let`是`match`的一个语法糖,它当值匹配某一模式时执行代码而忽略所有其他值。 换句话说,可以认为`if let`是`match`的一个语法糖,它当值匹配某一模式时执行代码而忽略所有其他值。

@ -237,7 +237,7 @@ character boundary', ../src/libcore/str/mod.rs:1694
幸运的是,这里还有其他获取字符串元素的方式。 幸运的是,这里还有其他获取字符串元素的方式。
如果你需要操作单独的 Unicode 标量值,最好的选择是使用`chars`方法。“नमस्ते”调用`chars`方法会将其分开并返回六个`char`类型的值,接着就可以遍历结果来访问每一个元素了: 如果你需要操作单独的 Unicode 标量值,最好的选择是使用`chars`方法。“नमस्ते”调用`chars`方法会将其分开并返回六个`char`类型的值,接着就可以遍历结果来访问每一个元素了:
```rust ```rust
for c in "नमस्ते".chars() { for c in "नमस्ते".chars() {

@ -396,7 +396,7 @@ fn first_word<'a>(s: &'a str) -> &'a str {
这里我们提到一些 Rust 的历史是因为更多的明确的模式将被合并和添加到编译器中是完全可能的。未来将会需要越来越少的生命周期注解。 这里我们提到一些 Rust 的历史是因为更多的明确的模式将被合并和添加到编译器中是完全可能的。未来将会需要越来越少的生命周期注解。
被编码进 Rust 引用分析的模式被称为**生命周期省略规则***lifetime elision rules*)。这并不是需要程序员遵守的规则;这些规则是一系列特定的场景,此时编译器会考虑,如果代码符合这些场景,就不需要明确指定生命周期。 被编码进 Rust 引用分析的模式被称为**生命周期省略规则***lifetime elision rules*)。这并不是需要程序员遵守的规则;这些规则是一系列特定的场景,此时编译器会考虑,如果代码符合这些场景,就不需要明确指定生命周期。
这些规则并不提供完整的推断:如果 Rust 在明确遵守这些规则的前提下变量的生命周期仍然是模棱两可的话,它不会猜测剩余引用的生命周期应该是什么。在这种情况,编译器会给出一个错误,这可以通过增加对应引用之间相联系的生命周期注解来解决。 这些规则并不提供完整的推断:如果 Rust 在明确遵守这些规则的前提下变量的生命周期仍然是模棱两可的话,它不会猜测剩余引用的生命周期应该是什么。在这种情况,编译器会给出一个错误,这可以通过增加对应引用之间相联系的生命周期注解来解决。

@ -149,7 +149,7 @@ fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
<!-- Will add wingdings in libreoffice /Carol --> <!-- Will add wingdings in libreoffice /Carol -->
`lines`方法返回一个迭代器。第十三会深入了解迭代器,不过我们已经在列表 3-6 中见过使用迭代器的方法,在那里使用了一个`for`循环和迭代器在一个集合的每一项上运行一些代码。 `lines`方法返回一个迭代器。第十三会深入了解迭代器,不过我们已经在列表 3-6 中见过使用迭代器的方法,在那里使用了一个`for`循环和迭代器在一个集合的每一项上运行一些代码。
<!-- so what does `lines` do on its own, if we need to use it in a for loop to <!-- so what does `lines` do on its own, if we need to use it in a for loop to
work? --> work? -->

@ -21,7 +21,7 @@
Rust 编译器执行的静态分析天生是保守的。代码的一些属性则不可能通过分析代码发现:其中最著名的就是停机问题(停机问题),这超出了本书的范畴,不过如果你感兴趣的话这是一个值得研究的有趣主题。 Rust 编译器执行的静态分析天生是保守的。代码的一些属性则不可能通过分析代码发现:其中最著名的就是停机问题(停机问题),这超出了本书的范畴,不过如果你感兴趣的话这是一个值得研究的有趣主题。
因为一些分析是不可能的Rust 编译器在其不确定的时候甚至都不尝试猜测,所以说它是保守的而且有时会拒绝事实上不会违反 Rust 保证的正确的程序。换句话说,如果 Rust 接受不正确的程序,那么人们也就不会相信 Rust 所做的保证了。如果 Rust 拒绝正确的程序,会给程序员带来不,但不会带来灾难。`RefCell<T>`正是用于当你知道代码遵守借用规则,而编译器不能理解的时候。 因为一些分析是不可能的Rust 编译器在其不确定的时候甚至都不尝试猜测,所以说它是保守的而且有时会拒绝事实上不会违反 Rust 保证的正确的程序。换句话说,如果 Rust 接受不正确的程序,那么人们也就不会相信 Rust 所做的保证了。如果 Rust 拒绝正确的程序,会给程序员带来不便,但不会带来灾难。`RefCell<T>`正是用于当你知道代码遵守借用规则,而编译器不能理解的时候。
类似于`Rc<T>``RefCell<T>`只能用于单线程场景。在并发章节会介绍如何在多线程程序中使用`RefCell<T>`的功能。现在所有你需要知道的就是如果尝试在多线程上下文中使用`RefCell<T>`,会得到一个编译错误。 类似于`Rc<T>``RefCell<T>`只能用于单线程场景。在并发章节会介绍如何在多线程程序中使用`RefCell<T>`的功能。现在所有你需要知道的就是如果尝试在多线程上下文中使用`RefCell<T>`,会得到一个编译错误。

@ -104,7 +104,7 @@ pointing to each other</span>
Rust 标准库中提供了`Weak<T>`,一个用于存在引用循环但只有一个方向有所有权的智能指针。我们已经展示过如何克隆`Rc<T>`来增加引用的`strong_count``Weak<T>`是一种引用`Rc<T>`但不增加`strong_count`的方式:相反它增加`Rc`引用的`weak_count`。当`Rc`离开作用域,其内部值会在`strong_count`为 0 的时候被丢弃,即便`weak_count`不为 0 。为了能够从`Weak<T>`中获取值,首先需要使用`upgrade`方法将其升级为`Option<Rc<T>>`。升级`Weak<T>`的结果在`Rc`还未被丢弃时是`Some`,而在`Rc`被丢弃时是`None`。因为`upgrade`返回一个`Option`,我们知道 Rust 会确保`Some`和`None`的情况都被处理并不会尝试使用一个无效的指针。 Rust 标准库中提供了`Weak<T>`,一个用于存在引用循环但只有一个方向有所有权的智能指针。我们已经展示过如何克隆`Rc<T>`来增加引用的`strong_count``Weak<T>`是一种引用`Rc<T>`但不增加`strong_count`的方式:相反它增加`Rc`引用的`weak_count`。当`Rc`离开作用域,其内部值会在`strong_count`为 0 的时候被丢弃,即便`weak_count`不为 0 。为了能够从`Weak<T>`中获取值,首先需要使用`upgrade`方法将其升级为`Option<Rc<T>>`。升级`Weak<T>`的结果在`Rc`还未被丢弃时是`Some`,而在`Rc`被丢弃时是`None`。因为`upgrade`返回一个`Option`,我们知道 Rust 会确保`Some`和`None`的情况都被处理并不会尝试使用一个无效的指针。
不同于列表 15-17 中每个项只知道它的下一项,加入我们需要一个树,它的项知道它的子项**和**父项。 不同于列表 15-17 中每个项只知道它的下一项,假如我们需要一个树,它的项知道它的子项**和**父项。
让我们从一个叫做`Node`的存放拥有所有权的`i32`值和其子`Node`值的引用的结构体开始: 让我们从一个叫做`Node`的存放拥有所有权的`i32`值和其子`Node`值的引用的结构体开始:
@ -276,4 +276,4 @@ examining strong and weak reference counts of `leaf` and `branch`</span>
[The Nomicon]: https://doc.rust-lang.org/stable/nomicon/vec.html [The Nomicon]: https://doc.rust-lang.org/stable/nomicon/vec.html
接下来,让我们谈谈 Rust 的并发。我们还会学习到一些新的并发有帮助的智能指针。 接下来,让我们谈谈 Rust 的并发。我们还会学习到一些新的并发有帮助的智能指针。

@ -4,14 +4,14 @@
> <br> > <br>
> commit da15de39eaabd50100d6fa662c653169254d9175 > commit da15de39eaabd50100d6fa662c653169254d9175
确保内存安全并不是 Rust 的唯一目标:作为一个能更好的处理并发和并行编程一直是 Rust 的另一个主要目标。 确保内存安全并不是 Rust 的唯一目标:更好的处理并发和并行编程一直是 Rust 的另一个主要目标。
**并发编程**concurrent programming代表程序的不同部分相互独立的执行而**并行编程**代表程序不同部分同时执行这两个概念在计算机拥有更多处理器可供程序利用时变得更加重要。由于历史的原因在此类上下文中编程一直是困难且容易出错的Rust 希望能改变这一点。 **并发编程**concurrent programming代表程序的不同部分相互独立的执行而**并行编程**代表程序不同部分同时执行这两个概念在计算机拥有更多处理器可供程序利用时变得更加重要。由于历史的原因在此类上下文中编程一直是困难且容易出错的Rust 希望能改变这一点。
最开始,我们认为内存安全和防止并发问题是需要通过两个不同的方法解决的两个相互独立的挑战。然而,随着时间的推移,我们发现所有权和类型系统是一系列解决内存安全**和**并发问题的强用力的工具!通过改进所有权和类型检查,很多并发错误在 Rust 中都是**编译时**错误,而不是运行时错误。我们给 Rust 的这一部分起了一个绰号**无畏并发***fearless concurrency*)。无畏并发意味着 Rust 不光允许你自信代码不会出现诡异的错误,也让你可以轻易重构这种代码而无需担心会引入新的 bug。 最开始,我们认为内存安全和防止并发问题是需要通过两个不同的方法解决的两个相互独立的挑战。然而,随着时间的推移,我们发现所有权和类型系统是一系列解决内存安全**和**并发问题的强用力的工具!通过改进所有权和类型检查,很多并发错误在 Rust 中都是**编译时**错误,而不是运行时错误。我们给 Rust 的这一部分起了一个绰号**无畏并发***fearless concurrency*)。无畏并发意味着 Rust 不光允许你自信代码不会出现诡异的错误,也让你可以轻易重构这种代码而无需担心会引入新的 bug。
> 注意:对于 Rust 的口号**无畏并发**,这里用**并发**指代很多问题而不是更精确的区分**并发和(或)并行**,是出于简化问题的原因。如果这是一本专注于并发和/或并行的书,我们肯定会更精确的。对于本章,当我们谈到**并发**时,请自行替换为**并发和(或)并行**。 > 注意:对于 Rust 的口号**无畏并发**,这里用**并发**指代很多问题而不是更精确的区分**并发和(或)并行**,是出于简化问题的原因。如果这是一本专注于并发和/或并行的书,我们肯定会更精确的。对于本章,当我们谈到**并发**时,请自行替换为**并发和(或)并行**。
很多语言所提供的处理并发问题的解决方法是非常有自身特色的。这是一个非常合理的策略尤其是对于更高级的语言来说不过对于底层语言来说可没有奢侈的选择。底层语言被期望为在任何给定的情况下都可以使能提供最高性能的解决方案可行同时他们对硬件有更少的抽象。因此Rust 给了我们多种工具并以各种适合我们的情况和要求的方式来为问题建模。 很多语言所提供的处理并发问题的解决方法都非常有特色尤其是对于更高级的语言这是一个非常合理的策略。然而对于底层语言则没有奢侈的选择。在任何给定的情况下我们都期望底层语言可以提供最高的性能并且对硬件有更薄的抽象。因此Rust 给了我们多种工具,并以适合实际情况和需求的方式来为问题建模。
如下是本章将要涉及到的内容: 如下是本章将要涉及到的内容:

@ -222,7 +222,7 @@ fn main() {
<span class="caption">Listing 16-4: A thread with a closure that attempts to <span class="caption">Listing 16-4: A thread with a closure that attempts to
capture a reference to `v` from a main thread that drops `v`</span> capture a reference to `v` from a main thread that drops `v`</span>
这些代码可以运行,而新建线程则可能直接就出错了并完全没有机会运行。新建线程内部有一个`v`的引用,不过主线程仍在执行:它立刻丢弃了`v`,使用了第十五章提到的显式丢弃其参数的`drop`函数。接着,新建线程开始执行,现在`v`是无效的了,所以它的引用也就是无效的。噢,这太糟了! 这些代码可以运行,而新建线程则可能直接就出错了并完全没有机会运行。新建线程内部有一个`v`的引用,不过主线程仍在执行:它立刻丢弃了`v`,使用了第十五章提到的显式丢弃其参数的`drop`函数。接着,新建线程开始执行,现在`v`是无效的了,所以它的引用也就是无效的。噢,这太糟了!
为了修复这个问题,我们可以听取错误信息的建议: 为了修复这个问题,我们可以听取错误信息的建议:

@ -52,14 +52,16 @@ single threaded context for simplicity</span>
像很多类型一样,我们使用关联函数 `new` 来创建一个 `Mutex<T>`。使用`lock`方法获取锁,以访问互斥器中的数据。这个调用会阻塞,直到我们拥有锁为止。如果另一个线程拥有锁,并且那个线程 panic 了,则这个调用会失败。类似于列表 16-6 那样,我们暂时使用 `unwrap()` 进行错误处理,或者使用第九章中提及的更好的工具。 像很多类型一样,我们使用关联函数 `new` 来创建一个 `Mutex<T>`。使用`lock`方法获取锁,以访问互斥器中的数据。这个调用会阻塞,直到我们拥有锁为止。如果另一个线程拥有锁,并且那个线程 panic 了,则这个调用会失败。类似于列表 16-6 那样,我们暂时使用 `unwrap()` 进行错误处理,或者使用第九章中提及的更好的工具。
一旦获取了锁,就可以将返回值(在这里是`num`)作为一个数据的可变引用来使用了。类型系统是 Rust 如何保证使用值之前必须获取锁的:`Mutex<i32>`并不是一个`i32`,所以**必须**获取锁才能使用这个`i32`值。我们是不会忘记这么做的;因为类型系统是不会允许的。
与你可能怀疑的一样,`Mutex<T>`是一个智能指针。好吧,更准确的说,`lock`调用返回一个叫做`MutexGuard`的智能指针。类似我们在第十五章见过的智能指针,它实现了`Deref`来指向其内部数据。另外`MutexGuard`有一个用来释放锁的`Drop`实现。这样就不会忘记释放锁了。这在`MutexGuard`离开作用域时会自动发生,例如它发生于列表 16-12 中内部作用域的结尾。接着可以打印出互斥器的值并发现能够将其内部的`i32`改为 6。 一旦获取了锁,就可以将返回值(在这里是`num`)作为一个数据的可变引用使用了。观察 Rust 类型系统如何保证使用值之前必须获取锁:`Mutex<i32>`并不是一个`i32`,所以**必须**获取锁才能使用这个`i32`值。我们是不会忘记这么做的,因为类型系统不允许。
你也许会怀疑,`Mutex<T>`是一个智能指针?是的!更准确的说,`lock`调用返回一个叫做`MutexGuard`的智能指针。类似我们在第十五章见过的智能指针,它实现了`Deref`来指向其内部数据。另外`MutexGuard`有一个用来释放锁的`Drop`实现。这样就不会忘记释放锁了。这在`MutexGuard`离开作用域时会自动发生,例如它发生于列表 16-12 中内部作用域的结尾。接着可以打印出互斥器的值并发现能够将其内部的`i32`改为 6。
#### 在线程间共享`Mutex<T>` #### 在线程间共享`Mutex<T>`
现在让我们尝试使用`Mutex<T>`在多个线程间共享值。我们将启动十个线程,并在每一个线程中对一个计数器值加一,这样计数器将从 0 变为 10。注意接下来的几个例子会有编译错误而我们将利用这些错误来学习如何使用 现在让我们尝试使用`Mutex<T>`在多个线程间共享值。我们将启动十个线程,并在各个线程中对同一个计数器值加一,这样计数器将从 0 变为 10。注意接下来的几个例子会出现编译错误而我们将通过这些错误来学习如何使用
`Mutex<T>`以及 Rust 又是怎样帮助我们正确使用它的。列表 16-13 是最开始的例子: `Mutex<T>`,以及 Rust 又是如何辅助我们以确保正确。列表 16-13 是最开始的例子:
<span class="filename">Filename: src/main.rs</span> <span class="filename">Filename: src/main.rs</span>
@ -91,9 +93,9 @@ fn main() {
<span class="caption">Listing 16-13: The start of a program having 10 threads <span class="caption">Listing 16-13: The start of a program having 10 threads
each increment a counter guarded by a `Mutex<T>`</span> each increment a counter guarded by a `Mutex<T>`</span>
这里创建了一个`counter`变量来存放内含`i32`的`Mutex<T>`,类似列表 16-12 那样。接下来使用 range 创建了 10 个线程。这里使用了`thread::spawn`并对所有线程使用了相同的闭包:他们每一个都将调用`lock`方法来获取`Mutex<T>`上的锁并对接着互斥器中的值加一。当一个线程结束执行其闭包`num`会离开作用域并释放锁这样另一个线程就可以获取它了。 这里创建了一个 `counter` 变量来存放内含 `i32` `Mutex<T>`,类似列表 16-12 那样。接下来使用 range 创建了 10 个线程。使用了 `thread::spawn` 并对所有线程使用了相同的闭包:他们每一个都将调用 `lock` 方法来获取 `Mutex<T>` 上的锁,接着将互斥器中的值加一。当一个线程结束执行,`num` 会离开闭包作用域并释放锁这样另一个线程就可以获取它了。
在主线程中,我们像列表 16-2 那样收集了所有的 join 句柄,并接着每一个的`join`方法来确保所有线程都会结束。那时,主线程会获取锁并打印出程序的结果。 在主线程中,我们像列表 16-2 那样收集了所有的 join 句柄,调用它们的 `join` 方法来确保所有线程都会结束。之后,主线程会获取锁并打印出程序的结果。
之前提示过这个例子不能编译,让我们看看为什么! 之前提示过这个例子不能编译,让我们看看为什么!
@ -112,13 +114,13 @@ referenced variables), use the `move` keyword, as shown:
| let handle = thread::spawn(move || { | let handle = thread::spawn(move || {
``` ```
这类似于列表 16-5 中解决了的问题。考虑到启动了多个线程Rust 无法知道这些线程会运行多久而`counter`是否在每一个线程尝试借用它时仍然保持有效。帮助信息提醒了我们如何解决它:可以使用`move`来给予每个线程其所有权。试试将这个修改用到闭包上 这类似于列表 16-5 中解决了的问题。考虑到启动了多个线程Rust 无法知道这些线程会运行多久,而在每一个线程尝试借用 `counter` 时它是否仍然有效。帮助信息提醒了我们如何解决它:可以使用 `move` 来给予每个线程其所有权。尝试在闭包上做一点改动
```rust,ignore ```rust,ignore
thread::spawn(move || { thread::spawn(move || {
``` ```
再次尝试编译。这会出现了一个不同的错误! 再次编译。这回出现了一个不同的错误!
``` ```
error[E0382]: capture of moved value: `counter` error[E0382]: capture of moved value: `counter`
@ -147,9 +149,9 @@ error[E0382]: use of moved value: `counter`
error: aborting due to 2 previous errors error: aborting due to 2 previous errors
``` ```
`move`并没有像列表 16-5 中那样解决这个程序中的问题。为什么没有呢?这个错误信息有些难以理解,因为它表明`counter`被移动进了闭包,接着它在调用`lock`时被捕获。这听起来像是我们希望的,不过这是不允许的 `move` 并没有像列表 16-5 中那样解决问题。为什么呢?错误信息有点难懂,因为它表明 `counter` 被移动进了闭包,接着它在调用 `lock` 时被捕获。这似乎是我们希望的,然而不被允许
让我们推理一下。现在不再使用`for`循环创建 10 个线程,让我们不用循环而只创建两个线程来看看会发生什么。将列表 16-13 中第一个`for`循环替换为如下代码: 让我们推理一下。这次不再使用 `for` 循环创建 10 个线程,只创建两个线程,看看会发生什么。将列表 16-13 中第一个`for`循环替换为如下代码:
```rust,ignore ```rust,ignore
let handle = thread::spawn(move || { let handle = thread::spawn(move || {
@ -167,7 +169,7 @@ let handle2 = thread::spawn(move || {
handles.push(handle2); handles.push(handle2);
``` ```
这里创建了两个线程,并将用于第二个线程的变量名改为`handle2`和`num2`。现在我们简化了例子来看看是否能够理解错误信息。这一次编译给出如下信息: 这里创建了两个线程,并将第二个线程所用的变量改名为 `handle2``num2`。我们简化了例子,看是否能理解错误信息。此次编译给出如下信息:
```text ```text
error[E0382]: capture of moved value: `counter` error[E0382]: capture of moved value: `counter`
@ -176,8 +178,8 @@ error[E0382]: capture of moved value: `counter`
8 | let handle = thread::spawn(move || { 8 | let handle = thread::spawn(move || {
| ------- value moved (into closure) here | ------- value moved (into closure) here
... ...
16 | let mut num2 = counter.lock().unwrap(); 16 | let mut num = counter.lock().unwrap();
| ^^^^^^^ value captured here after move | ^^^^^^^ value captured here after move
| |
= note: move occurs because `counter` has type `std::sync::Mutex<i32>`, = note: move occurs because `counter` has type `std::sync::Mutex<i32>`,
which does not implement the `Copy` trait which does not implement the `Copy` trait
@ -197,11 +199,11 @@ error[E0382]: use of moved value: `counter`
error: aborting due to 2 previous errors error: aborting due to 2 previous errors
``` ```
啊哈!在第一个错误信息中Rust 表明了`counter`被移动进了`handle`所代表线程的闭包中。这个移动阻止我们在第二个线程中对其调用`lock`并将结果储存在`num2`中时捕获`counter`!所以 Rust 告诉我们不能将`counter`的所有权移动到多个线程中。这在之前很难看出是因为我们在循环中创建多个线程,而 Rust 无法在循环的迭代中指明不同的线程(没有临时变量`num2`)。 啊哈!第一个错误信息中说,`counter` 被移动进了 `handle` 所代表线程的闭包中。因此我们无法在第二个线程中对其调用 `lock`,并将结果储存在 `num2` 中时捕获`counter`!所以 Rust 告诉我们不能将 `counter` 的所有权移动到多个线程中。这在之前很难看出,因为我们在循环中创建了多个线程,而 Rust 无法在每次迭代中指明不同的线程(没有临时变量 `num2`)。
#### 多线程和多所有权 #### 多线程和多所有权
在第十五章中,我们可以通过使用智能指针`Rc<T>`来创建引用计数的值来拥有多所有权。同时第十五章提到了`Rc<T>`只能用于单线程上下文,不过还是让我们在这里试用`Rc<T>`来观察会发生什么。列表 16-14 将`Mutex<T>`封装进了`Rc<T>`中,并在移动到线程中之前克隆了`Rc<T>`。切换回循环来创建线程,并保留闭包中的`move`关键字: 在第十五章中,我们通过使用智能指针 `Rc<T>` 来创建引用计数的值,以便拥有多所有权。同时第十五章提到了 `Rc<T>` 只能在单线程环境中使用,不过还是在这里试用 `Rc<T>` 看看会发生什么。列表 16-14 将 `Mutex<T>` 装进了 `Rc<T>` 中,并在移入线程之前克隆了 `Rc<T>`。再用循环来创建线程,保留闭包中的 `move` 关键字:
<span class="filename">Filename: src/main.rs</span> <span class="filename">Filename: src/main.rs</span>
@ -235,7 +237,7 @@ fn main() {
<span class="caption">Listing 16-14: Attempting to use `Rc<T>` to allow <span class="caption">Listing 16-14: Attempting to use `Rc<T>` to allow
multiple threads to own the `Mutex<T>`</span> multiple threads to own the `Mutex<T>`</span>
又一次,编译并...出现了不同的错误!编译器真是教会了我们很多东西 再一次编译并...出现了不同的错误!编译器真是教会了我们很多!
``` ```
error[E0277]: the trait bound `std::rc::Rc<std::sync::Mutex<i32>>: error[E0277]: the trait bound `std::rc::Rc<std::sync::Mutex<i32>>:
@ -254,11 +256,11 @@ std::marker::Send` is not satisfied
= note: required by `std::thread::spawn` = note: required by `std::thread::spawn`
``` ```
哇哦,太长不看!需要指出一些重要的部分:第一个提示表明`Rc<Mutex<i32>>`不能安全的在线程间传递。理由也在错误信息中,经过提取之后,表明“不满足`Send` trait bound”`the trait bound Send is not satisfied`)。下一部分将会讨论`Send`,它是许多确保用在多线程中的类型能够适合并发环境的 trait 之一。 哇哦,太长不看!说重点:第一个提示表明 `Rc<Mutex<i32>>` 不能安全的在线程间传递。理由也在错误信息中,“不满足 `Send` trait bound”`the trait bound Send is not satisfied`)。下一部分将会讨论 `Send`,它是确保许多用在多线程中的类型能够适合并发环境的 trait 之一。
不幸的是,`Rc<T>`并不能安全的在线程间共享。当`Rc<T>`管理引用计数时,它必须在每一个`clone`调用时增加计数并在每一个克隆被丢弃时减少计数。`Rc<T>`并没有使用任何并发原语来确保改变计数的操作不会被其他线程打断。在计数出错时可能会导致诡异的 bug比如可能会造成内存泄漏或在使用结束之前就丢弃一个值。那么如果有一个正好与`Rc<T>`类似,不过以一种线程安全的方式改变引用计数的类型会怎么样呢? 不幸的是,`Rc<T>` 并不能安全的在线程间共享。当 `Rc<T>` 管理引用计数时,它必须在每一个 `clone` 调用时增加计数,并在每一个克隆被丢弃时减少计数。`Rc<T>` 并没有使用任何并发原语来确保改变计数的操作不会被其他线程打断。在计数出错时可能会导致诡异的 bug比如可能会造成内存泄漏或在使用结束之前就丢弃一个值。那么如果有一个正好与 `Rc<T>` 类似,不过以一种线程安全的方式改变引用计数的类型会怎么样呢?
#### 原子引用计数`Arc<T>` #### 原子引用计数 `Arc<T>`
如果你思考过像之前那样的问题的话,你就是正确的。确实有一个类似`Rc<T>`并可以安全的用于并发环境的类型:`Arc<T>`。字母“a”代表**原子性***atomic*),所以这是一个**原子引用计数***atomically reference counted*)类型。原子性是另一类这里还未涉及到的并发原语;请查看标准库中`std::sync::atomic`的文档来获取更多细节。其中的要点就是:原子性类型工作起来类似原始类型,不过可以安全的在线程间共享。 如果你思考过像之前那样的问题的话,你就是正确的。确实有一个类似`Rc<T>`并可以安全的用于并发环境的类型:`Arc<T>`。字母“a”代表**原子性***atomic*),所以这是一个**原子引用计数***atomically reference counted*)类型。原子性是另一类这里还未涉及到的并发原语;请查看标准库中`std::sync::atomic`的文档来获取更多细节。其中的要点就是:原子性类型工作起来类似原始类型,不过可以安全的在线程间共享。

Loading…
Cancel
Save