diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..f37fa75 Binary files /dev/null and b/.DS_Store differ diff --git a/src/ch15-03-drop.md b/src/ch15-03-drop.md index 0456929..ad41544 100644 --- a/src/ch15-03-drop.md +++ b/src/ch15-03-drop.md @@ -12,7 +12,7 @@ 示例 15-8 展示了并没有实际功能的结构体`CustomSmartPointer`,不过我们会在创建实例之后打印出`CustomSmartPointer created.`,而在实例离开作用域时打印出`Dropping CustomSmartPointer!`,这样就能看出每一段代码是何时被执行的。实际的项目中,我们应该在`drop`中清理任何智能指针运行所需要的资源,而不是这个例子中的`println!`语句: -Filename: src/main.rs +文件名: src/main.rs ```rust struct CustomSmartPointer { @@ -32,9 +32,7 @@ fn main() { } ``` -Listing 15-8: A `CustomSmartPointer` struct that -implements the `Drop` trait, where we could put code that would clean up after -the `CustomSmartPointer`. +示例 15-8: 结构体 `CustomSmartPointer` 实现了 `Drop` trait, 我们能够放入代码以便在 `CustomSmartPointer` 离开作用域后进行清理 `Drop` trait 位于 prelude 中,所以无需导入它。`drop`方法的实现调用了`println!`;这里是你需要放入实际关闭套接字代码的地方。在`main`函数中,我们创建一个`CustomSmartPointer`的新实例并打印出`CustomSmartPointer created.`以便在运行时知道代码运行到此处。在`main`的结尾,`CustomSmartPointer`的实例会离开作用域。注意我们没有显式调用`drop`方法: @@ -50,7 +48,7 @@ Dropping CustomSmartPointer! 可以使用`std::mem::drop`函数来在值离开作用域之前丢弃它。这通常是不必要的;整个`Drop` trait 的要点在于它自动的帮我们处理清理工作。在第十六章讲到并发时我们会看到一个需要在离开作用域之前丢弃值的例子。现在知道这是可能的即可,`std::mem::drop`位于 prelude 中所以可以如示例 15-9 所示直接调用`drop`: -Filename: src/main.rs +文件名: src/main.rs ```rust,ignore fn main() { @@ -61,8 +59,7 @@ fn main() { } ``` -Listing 15-9: Calling `std::mem::drop` to explicitly drop -a value before it goes out of scope +示例 15-9: 在一个值离开作用域之前,调用 `std::mem::drop` 显式进行回收 运行这段代码会打印出如下内容,因为`Dropping CustomSmartPointer!`在`CustomSmartPointer created.`和`Wait for it...`之间被打印出来,表明析构代码被执行了: diff --git a/src/ch15-04-rc.md b/src/ch15-04-rc.md index d735eba..861f77a 100644 --- a/src/ch15-04-rc.md +++ b/src/ch15-04-rc.md @@ -18,12 +18,11 @@ Two lists that share ownership of a third list -Figure 15-10: Two lists, `b` and `c`, sharing ownership -of a third list, `a` +图 15-10: 两个列表, `b` 和 `c`, 共享第三个列表 `a` 的所有权 尝试使用`Box`定义的`List`并不能工作,如示例 15-11 所示: -Filename: src/main.rs +文件: src/main.rs ```rust,ignore enum List { @@ -42,8 +41,7 @@ fn main() { } ``` -Listing 15-11: Having two lists using `Box` that try -to share ownership of a third list won't work +示例 15-11: 具有两个使用 `Box` 的列表,试图共享第三个列表的所有权,是不能正常运行的/span> 编译会得出如下错误: @@ -66,7 +64,7 @@ error[E0382]: use of moved value: `a` 如示例 15-12 所示,可以将`List`的定义从`Box`改为`Rc`: -Filename: src/main.rs +文件名: src/main.rs ```rust enum List { @@ -84,8 +82,7 @@ fn main() { } ``` -Listing 15-12: A definition of `List` that uses -`Rc` +示例 15-12: 使用 `Rc` 定义的 `List` 注意必须为`Rc`增加`use`语句因为它不在 prelude 中。在`main`中创建了存放 5 和 10 的列表并将其存放在一个叫做`a`的新的`Rc`中。接着当创建`b`和`c`时,我们对`a`调用了`clone`方法。 @@ -93,7 +90,7 @@ fn main() { 之前我们见过`clone`方法,当时使用它来创建某些数据的完整拷贝。但是对于`Rc`来说,它并不创建一个完整的拷贝。`Rc`存放了**引用计数**,也就是说,一个存在多少个克隆的计数器。让我们像示例 15-13 那样在创建`c`时增加一个内部作用域,并在不同的位置打印出关联函数`Rc::strong_count`的结果。`Rc::strong_count`返回传递给它的`Rc`值的引用计数,而在本章的稍后部分介绍避免引用循环时讲到它为什么叫做`strong_count`。 -Filename: src/main.rs +文件名: src/main.rs ```rust # enum List { @@ -117,7 +114,7 @@ fn main() { } ``` -Listing 15-13: Printing out the reference count +示例 15-13: 打印输出引用计数 这会打印出: @@ -130,4 +127,4 @@ rc after c goes out of scope = 2 不难看出`a`的初始引用计数是一。接着每次调用`clone`,计数会加一。当`c`离开作用域时,计数减一,这发生在`Rc`的`Drop` trait 实现中。这个例子中不能看到的是当`b`接着是`a`在`main`函数的结尾离开作用域时,包含 5 和 10 的列表的引用计数会是 0,这时列表将被丢弃。这个策略允许拥有多个所有者,而引用计数会确保任何所有者存在时这个值保持有效。 -在本部分的开始,我们说`Rc`只允许程序的多个部分读取`Rc`中`T`的不可变引用。如果`Rc`允许一个可变引用,我们将遇到第四章讨论的借用规则所不允许的问题:两个指向同一位置的可变借用会导致数据竞争和不一致。不过可变数据是非常有用的!在下一部分,我们将讨论内部可变性模式和`RefCell`类型,它可以与`Rc`结合使用来处理不可变性的限制。 \ No newline at end of file +在本部分的开始,我们说`Rc`只允许程序的多个部分读取`Rc`中`T`的不可变引用。如果`Rc`允许一个可变引用,我们将遇到第四章讨论的借用规则所不允许的问题:两个指向同一位置的可变借用会导致数据竞争和不一致。不过可变数据是非常有用的!在下一部分,我们将讨论内部可变性模式和`RefCell`类型,它可以与`Rc`结合使用来处理不可变性的限制。 diff --git a/src/ch15-05-interior-mutability.md b/src/ch15-05-interior-mutability.md index 55cb8bd..d2fcda3 100644 --- a/src/ch15-05-interior-mutability.md +++ b/src/ch15-05-interior-mutability.md @@ -29,7 +29,7 @@ Rust 编译器执行的静态分析天生是保守的。代码的一些属性则 示例 15-14 展示了如何使用`RefCell`来使函数不可变的和可变的借用它的参数。注意`data`变量使用`let data`而不是`let mut data`来声明为不可变的,而`a_fn_that_mutably_borrows`则允许可变的借用数据并修改它! -Filename: src/main.rs +文件名: src/main.rs ```rust use std::cell::RefCell; @@ -54,7 +54,7 @@ fn main() { } ``` -Listing 15-14: Using `RefCell`, `borrow`, and +示例 15-14: 使用 `RefCell`, `borrow` 和 `borrow_mut` 这个例子打印出: @@ -122,7 +122,7 @@ note: Run with `RUST_BACKTRACE=1` for a backtrace. 那么为什么要权衡考虑选择引入`RefCell`呢?好吧,还记得我们说过`Rc`只能拥有一个`T`的不可变引用吗?考虑到`RefCell`是不可变的,但是拥有内部可变性,可以将`Rc`与`RefCell`结合来创造一个既有引用计数又可变的类型。示例 15-15 展示了一个这么做的例子,再次回到示例 15-5 中的 cons list。在这个例子中,不同于在 cons list 中储存`i32`值,我们储存一个`Rc>`值。希望储存这个类型是因为其可以拥有不属于列表一部分的这个值的所有者(`Rc`提供的多个所有者功能),而且还可以改变内部的`i32`值(`RefCell`提供的内部可变性功能): -Filename: src/main.rs +文件名: src/main.rs ```rust #[derive(Debug)] @@ -152,8 +152,7 @@ fn main() { } ``` -Listing 15-15: Using `Rc>` to create a -`List` that we can mutate +示例 15-15: 使用 `Rc>` 创建能够修改的 `List` 我们创建了一个值,它是`Rc>`的实例。将其储存在变量`value`中因为我们希望之后能直接访问它。接着在`a`中创建了一个拥有存放了`value`值的`Cons`成员的`List`,而且`value`需要被克隆因为我们希望除了`a`之外还拥有`value`的所有权。接着将`a`封装进`Rc`中这样就可以创建都引用`a`的有着不同开头的列表`b`和`c`,类似示例 15-12 中所做的那样。 diff --git a/src/ch15-06-reference-cycles.md b/src/ch15-06-reference-cycles.md index 76bc639..39587d4 100644 --- a/src/ch15-06-reference-cycles.md +++ b/src/ch15-06-reference-cycles.md @@ -10,7 +10,7 @@ 在示例 15-16 中,我们将使用示例 15-5 中`List`定义的另一个变体。我们将回到储存`i32`值作为`Cons`成员的第一个元素。现在`Cons`成员的第二个元素是`RefCell>`:这时就不能修改`i32`值了,但是能够修改`Cons`成员指向的那个`List`。还需要增加一个`tail`方法来方便我们在拥有一个`Cons`成员时访问第二个项: -Filename: src/main.rs +文件名: src/main.rs ```rust,ignore #[derive(Debug)] @@ -29,12 +29,11 @@ impl List { } ``` -Listing 15-16: A cons list definition that holds a -`RefCell` so that we can modify what a `Cons` variant is referring to +示例 15-16: 持有 `RefCell` 的 cons 列表定义,我们不能修改 `Cons` 变体引用的内容/span> 接下来,在示例 15-17 中,我们将在变量`a`中创建一个`List`值,其内部是一个`5, Nil`的列表。接着在变量`b`创建一个值 10 和指向`a`中列表的`List`值。最后修改`a`指向`b`而不是`Nil`,这会创建一个循环: -Filename: src/main.rs +文件名: src/main.rs ```rust # #[derive(Debug)] @@ -82,15 +81,13 @@ fn main() { } ``` -Listing 15-17: Creating a reference cycle of two `List` -values pointing to each other +示例 15-17: 创建一个引用循环:两个`List` 的值互相指向彼此 使用`tail`方法来获取`a`中`RefCell`的引用,并将其放入变量`link`中。接着对`RefCell`使用`borrow_mut`方法将其中的值从存放`Nil`值的`Rc`改为`b`中的`Rc`。这创建了一个看起来像图 15-18 所示的引用循环: Reference cycle of lists -Figure 15-18: A reference cycle of lists `a` and `b` -pointing to each other +图 15-18: 列表 `a` 和 `b` 彼此互相指向形成引用循环 如果你注释掉最后的`println!`,Rust 会尝试打印出`a`指向`b`指向`a`这样的循环直到栈溢出。 @@ -122,7 +119,7 @@ struct Node { 我们希望能够`Node`拥有其子节点,同时也希望变量可以拥有每个节点以便可以直接访问他们。这就是为什么`Vec`中的项是`Rc`值。我们也希望能够修改其他节点的子节点,这就是为什么`children`中`Vec`被放进了`RefCell`的原因。在示例 15-19 中创建了一个叫做`leaf`的带有值 3 并没有子节点的`Node`实例,和另一个带有值 5 和以`leaf`作为子节点的实例`branch`: -Filename: src/main.rs +文件名: src/main.rs ```rust,ignore fn main() { @@ -138,9 +135,7 @@ fn main() { } ``` -Listing 15-19: Creating a `leaf` node and a `branch` node -where `branch` has `leaf` as one of its children but `leaf` has no reference to -`branch` +示例 15-19: 创建一个 `leaf` 节点 和一个 `branch` 节点,使 `leaf` 作为 `branch` 的一个孩子之一,但是`leaf` 没有持有 `branch` 的引用 `leaf`中的`Node`现在有两个所有者:`leaf`和`branch`,因为我们克隆了`leaf`中的`Rc`并储存在了`branch`中。`branch`中的`Node`知道它与`leaf`相关联因为`branch`在`branch.children`中有`leaf`的引用。然而,`leaf`并不知道它与`branch`相关联,而我们希望`leaf`知道`branch`是其父节点。 @@ -148,7 +143,7 @@ where `branch` has `leaf` as one of its children but `leaf` has no reference to 所以在`parent`的类型中是使用`Weak`而不是`Rc`,具体来说是`RefCell>`: -Filename: src/main.rs +文件名: src/main.rs ```rust use std::rc::{Rc, Weak}; @@ -164,7 +159,7 @@ struct Node { 这样,一个节点就能够在拥有父节点时指向它,而并不拥有其父节点。一个父节点哪怕在拥有指向它的子节点也会被丢弃,只要是其自身也没有一个父节点就行。现在将`main`函数更新为如示例 15-20 所示: -Filename: src/main.rs +文件名: src/main.rs ```rust,ignore fn main() { @@ -188,8 +183,8 @@ fn main() { } ``` -Listing 15-20: A `leaf` node and a `branch` node where -`leaf` has a `Weak` reference to its parent, `branch` +示例 15-20: 一个 `leaf` 节点和一个 `branch` 节点, +`leaf` 节点具有一个指向其父节点 `branch` 的 `Weak` 引用 创建`leaf`节点是类似的;因为它作为开始并没有父节点,这里创建了一个新的`Weak`引用实例。当尝试通过`upgrade`方法获取`leaf`父节点的引用时,会得到一个`None`值,如第一个`println!`输出所示: @@ -209,7 +204,7 @@ children: RefCell { value: [] } }] } }) 没有无限的输出(或直到栈溢出)的事实表明这里并没有引用循环。另一种证明的方式时观察调用`Rc::strong_count`和`Rc::weak_count`的值。在示例 15-21 中,创建了一个新的内部作用域并将`branch`的创建放入其中,这样可以观察`branch`被创建时和离开作用域被丢弃时发生了什么: -Filename: src/main.rs +文件名: src/main.rs ```rust,ignore fn main() { @@ -255,8 +250,7 @@ fn main() { } ``` -Listing 15-21: Creating `branch` in an inner scope and -examining strong and weak reference counts of `leaf` and `branch` +示例 15-21: 在内部范围创建一个 `branch` 节点,并检查 `leaf` 和 `branch` 的强弱引用计数 创建`leaf`之后,强引用计数是 1 (用于`leaf`自身)而弱引用计数是 0。在内部作用域中,在创建`branch`和关联`leaf`和`branch`之后,`branch`的强引用计数为 1(用于`branch`自身)而弱引用计数为 1(因为`leaf.parent`通过一个`Weak`指向`branch`)。`leaf`的强引用计数为 2,因为`branch`现在有一个`leaf`克隆的`Rc`储存在`branch.children`中。`leaf`的弱引用计数仍然为 0。 @@ -276,4 +270,4 @@ examining strong and weak reference counts of `leaf` and `branch` [The Nomicon]: https://doc.rust-lang.org/stable/nomicon/ -接下来,让我们谈谈 Rust 的并发。我们还会学习到一些新的对并发有帮助的智能指针。 \ No newline at end of file +接下来,让我们谈谈 Rust 的并发。我们还会学习到一些新的对并发有帮助的智能指针。 diff --git a/src/ch16-01-threads.md b/src/ch16-01-threads.md index 2881279..58ebd88 100644 --- a/src/ch16-01-threads.md +++ b/src/ch16-01-threads.md @@ -20,9 +20,9 @@ ### 使用`spawn`创建新线程 -为了创建一个新线程,调用`thread::spawn`函数并传递一个闭包(第十三章学习了闭包),它包含希望在新线程运行的代码。列表 16-1 中的例子在新线程中打印了一些文本而其余的文本在主线程中打印: +为了创建一个新线程,调用`thread::spawn`函数并传递一个闭包(第十三章学习了闭包),它包含希望在新线程运行的代码。示例 16-1 中的例子在新线程中打印了一些文本而其余的文本在主线程中打印: -Filename: src/main.rs +文件名: src/main.rs ```rust use std::thread; @@ -40,8 +40,7 @@ fn main() { } ``` -Listing 16-1: Creating a new thread to print one thing -while the main thread is printing something else +示例 16-1: 创建一个打印某些内容的新线程,但是主线程打印其它内容 注意这个函数编写的方式,当主线程结束时,它也会停止新线程。这个程序的输出每次可能都略微不同,不过它大体上看起来像这样: @@ -62,9 +61,9 @@ hi number 5 from the spawned thread! #### 使用`join`等待所有线程结束 -由于主线程先于新建线程结束,不仅列表 16-1 中的代码大部分时候不能保证新建线程执行完毕,甚至不能实际保证新建线程会被执行!可以通过保存`thread::spawn`的返回值来解决这个问题,这是一个`JoinHandle`。这看起来如列表 16-2 所示: +由于主线程先于新建线程结束,不仅示例 16-1 中的代码大部分时候不能保证新建线程执行完毕,甚至不能实际保证新建线程会被执行!可以通过保存`thread::spawn`的返回值来解决这个问题,这是一个`JoinHandle`。这看起来如示例 16-2 所示: -Filename: src/main.rs +文件名: src/main.rs ```rust use std::thread; @@ -84,8 +83,8 @@ fn main() { } ``` -Listing 16-2: Saving a `JoinHandle` from `thread::spawn` -to guarantee the thread is run to completion +示例 16-2: 从 `thread::spawn` 保存一个 `JoinHandle`, +以确保该线程能够运行至结束 `JoinHandle`是一个拥有所有权的值,它可以等待一个线程结束,这也正是`join`方法所做的。通过调用这个句柄的`join`,当前线程会阻塞直到句柄所代表的线程结束。因为我们将`join`调用放在了主线程的`for`循环之后,运行这个例子将产生类似这样的输出: @@ -109,7 +108,7 @@ hi number 9 from the spawned thread! 如果将`handle.join()`放在主线程的`for`循环之前,像这样: -Filename: src/main.rs +文件名: src/main.rs ```rust use std::thread; @@ -157,9 +156,9 @@ hi number 4 from the main thread! 现在我们正在创建新线程,所以让我们讨论一下获取环境值的闭包吧! -注意列表 16-1 中传递给`thread::spawn`的闭包并没有任何参数:并没有在新建线程代码中使用任何主线程的数据。为了在新建线程中使用来自于主线程的数据,需要新建线程的闭包获取它需要的值。列表 16-3 展示了一个尝试在主线程中创建一个 vector 并用于新建线程的例子,不过这么写还不能工作: +注意示例 16-1 中传递给`thread::spawn`的闭包并没有任何参数:并没有在新建线程代码中使用任何主线程的数据。为了在新建线程中使用来自于主线程的数据,需要新建线程的闭包获取它需要的值。示例 16-3 展示了一个尝试在主线程中创建一个 vector 并用于新建线程的例子,不过这么写还不能工作: -Filename: src/main.rs +文件名: src/main.rs ```rust,ignore use std::thread; @@ -175,8 +174,7 @@ fn main() { } ``` -Listing 16-3: Attempting to use a vector created by the -main thread from another thread +示例 16-3: 在主线程中创建一个 vector,尝试在其它线程中使用它/span> 闭包使用了`v`,所以闭包会获取`v`并使其成为闭包环境的一部分。因为`thread::spawn`在一个新线程中运行这个闭包,所以可以在新线程中访问`v`。 @@ -199,9 +197,9 @@ variables), use the `move` keyword, as shown: 当在闭包环境中获取某些值时,Rust 会尝试推断如何获取它。`println!`只需要`v`的一个引用,所以闭包尝试借用`v`。但是这有一个问题:我们并不知道新建线程会运行多久,所以无法知道`v`是否一直时有效的。 -考虑一下列表 16-4 中的代码,它展示了一个`v`的引用很有可能不再有效的场景: +考虑一下示例 16-4 中的代码,它展示了一个`v`的引用很有可能不再有效的场景: -Filename: src/main.rs +文件名: src/main.rs ```rust,ignore use std::thread; @@ -219,8 +217,7 @@ fn main() { } ``` -Listing 16-4: A thread with a closure that attempts to -capture a reference to `v` from a main thread that drops `v` +示例 16-4: 一个具有闭包的线程,尝试使用一个在主线程中被回收的引用 `v` 这些代码可以运行,而新建线程则可能直接就出错了并完全没有机会运行。新建线程内部有一个`v`的引用,不过主线程仍在执行:它立刻丢弃了`v`,使用了第十五章提到的显式丢弃其参数的`drop`函数。接着,新建线程开始执行,现在`v`是无效的了,所以它的引用也就是无效的。噢,这太糟了! @@ -232,9 +229,9 @@ variables), use the `move` keyword, as shown: | let handle = thread::spawn(move || { ``` -通过在闭包之前增加`move`关键字,我们强制闭包获取它使用的值的所有权,而不是引用借用。列表 16-5 中展示的对列表 16-3 代码的修改可以按照我们的预期编译并运行: +通过在闭包之前增加`move`关键字,我们强制闭包获取它使用的值的所有权,而不是引用借用。示例 16-5 中展示的对示例 16-3 代码的修改可以按照我们的预期编译并运行: -Filename: src/main.rs +文件名: src/main.rs ```rust use std::thread; @@ -250,10 +247,9 @@ fn main() { } ``` -Listing 16-5: Using the `move` keyword to force a closure -to take ownership of the values it uses +示例 16-5: 使用 `move` 关键字强制获取它使用的值的所有权 -那么列表 16-4 中那个主线程调用了`drop`的代码该怎么办呢?如果在闭包上增加了`move`,就将`v`移动到了闭包的环境中,我们将不能对其调用`drop`了。相反会出现这个编译时错误: +那么示例 16-4 中那个主线程调用了`drop`的代码该怎么办呢?如果在闭包上增加了`move`,就将`v`移动到了闭包的环境中,我们将不能对其调用`drop`了。相反会出现这个编译时错误: ``` error[E0382]: use of moved value: `v` @@ -271,4 +267,4 @@ error[E0382]: use of moved value: `v` Rust 的所有权规则又一次帮助了我们! -现在我们有一个线程和线程 API 的基本了解,让我们讨论一下使用线程实际可以**做**什么吧。 \ No newline at end of file +现在我们有一个线程和线程 API 的基本了解,让我们讨论一下使用线程实际可以**做**什么吧。 diff --git a/src/ch16-02-message-passing.md b/src/ch16-02-message-passing.md index 66a0b6b..b9ffa91 100644 --- a/src/ch16-02-message-passing.md +++ b/src/ch16-02-message-passing.md @@ -17,9 +17,9 @@ 我们将编写一个例子使用一个线程生成值并向通道发送他们。主线程会接收这些值并打印出来。 -首先,如列表 16-6 所示,先创建一个通道但不做任何事: +首先,如示例 16-6 所示,先创建一个通道但不做任何事: -Filename: src/main.rs +文件名: src/main.rs ```rust use std::sync::mpsc; @@ -30,16 +30,15 @@ fn main() { } ``` -Listing 16-6: Creating a channel and assigning the two -halves to `tx` and `rx` +示例 16-6: 创建一个通道,并指派一个包含 `tx` 和 `rx` 的元组 `mpsc::channel`函数创建一个新的通道。`mpsc`是**多个生产者,单个消费者**(*multiple producer, single consumer*)的缩写。简而言之,可以有多个产生值的**发送端**,但只能有一个消费这些值的**接收端**。现在我们以一个单独的生产者开始,不过一旦例子可以工作了就会增加多个生产者。 `mpsc::channel`返回一个元组:第一个元素是发送端,而第二个元素是接收端。由于历史原因,很多人使用`tx`和`rx`作为**发送者**和**接收者**的缩写,所以这就是我们将用来绑定这两端变量的名字。这里使用了一个`let`语句和模式来解构了元组。第十八章会讨论`let`语句中的模式和解构。 -让我们将发送端移动到一个新建线程中并发送一个字符串,如列表 16-7 所示: +让我们将发送端移动到一个新建线程中并发送一个字符串,如示例 16-7 所示: -Filename: src/main.rs +文件名: src/main.rs ```rust use std::thread; @@ -55,16 +54,15 @@ fn main() { } ``` -Listing 16-7: Moving `tx` to a spawned thread and sending -"hi" +示例 16-7: 将 `tx` 移动到一个新建的线程中并发送内容 "hi" 正如上一部分那样使用`thread::spawn`来创建一个新线程。并使用一个`move`闭包来将`tx`移动进闭包这样新建线程就是其所有者。 通道的发送端有一个`send`方法用来获取需要放入通道的值。`send`方法返回一个`Result`类型,因为如果接收端被丢弃了,将没有发送值的目标,所以发送操作会出错。在这个例子中,我们简单的调用`unwrap`来忽略错误,不过对于一个真实程序,需要合理的处理它。第九章是你复习正确错误处理策略的好地方。 -在列表 16-8 中,让我们在主线程中从通道的接收端获取值: +在示例 16-8 中,让我们在主线程中从通道的接收端获取值: -Filename: src/main.rs +文件名: src/main.rs ```rust use std::thread; @@ -83,12 +81,11 @@ fn main() { } ``` -Listing 16-8: Receiving the value "hi" in the main thread -and printing it out +示例 16-8: 在主线程中接收并打印内容 "hi" 通道的接收端有两个有用的方法:`recv`和`try_recv`。这里,我们使用了`recv`,它是 *receive* 的缩写。这个方法会阻塞执行直到从通道中接收一个值。一旦发送了一个值,`recv`会在一个`Result`中返回它。当通道发送端关闭,`recv`会返回一个错误。`try_recv`不会阻塞;相反它立刻返回一个`Result`。 -如果运行列表 16-8 中的代码,我们将会看到主线程打印出这个值: +如果运行示例 16-8 中的代码,我们将会看到主线程打印出这个值: ``` Got: hi @@ -96,9 +93,9 @@ Got: hi ### 通道与所有权如何交互 -现在让我们做一个试验来看看通道与所有权如何在一起工作:我们将尝试在新建线程中的通道中发送完`val`之后再使用它。尝试编译列表 16-9 中的代码: +现在让我们做一个试验来看看通道与所有权如何在一起工作:我们将尝试在新建线程中的通道中发送完`val`之后再使用它。尝试编译示例 16-9 中的代码: -Filename: src/main.rs +文件名: src/main.rs ```rust,ignore use std::thread; @@ -118,8 +115,7 @@ fn main() { } ``` -Listing 16-9: Attempting to use `val` after we have sent -it down the channel +示例 16-9: 在我们已经发送到通道中后,尝试使用 `val` 引用 这里尝试在通过`tx.send`发送`val`到通道中之后将其打印出来。这是一个坏主意:一旦将值发送到另一个线程后,那个线程可能会在我们在此使用它之前就修改或者丢弃它。这会由于不一致或不存在的数据而导致错误或意外的结果。 @@ -144,9 +140,9 @@ error[E0382]: use of moved value: `val` ### 发送多个值并观察接收者的等待 -列表 16-8 中的代码可以编译和运行,不过这并不是很有趣:通过它难以看出两个独立的线程在一个通道上相互通讯。列表 16-10 则有一些改进会证明这些代码是并发执行的:新建线程现在会发送多个消息并在每个消息之间暂停一段时间。 +示例 16-8 中的代码可以编译和运行,不过这并不是很有趣:通过它难以看出两个独立的线程在一个通道上相互通讯。示例 16-10 则有一些改进会证明这些代码是并发执行的:新建线程现在会发送多个消息并在每个消息之间暂停一段时间。 -Filename: src/main.rs +文件名: src/main.rs ```rust use std::thread; @@ -176,14 +172,13 @@ fn main() { } ``` -Listing 16-10: Sending multiple messages and pausing -between each one +示例 16-10: 发送多个消息,并在每次发送后暂停一段时间 这一次,在新建线程中有一个字符串 vector 希望发送到主线程。我们遍历他们,单独的发送每一个字符串并通过一个`Duration`值调用`thread::sleep`函数来暂停一秒。 在主线程中,不再显式的调用`recv`函数:而是将`rx`当作一个迭代器。对于每一个接收到的值,我们将其打印出来。当通道被关闭时,迭代器也将结束。 -当运行列表 16-10 中的代码时,将看到如下输出,每一行都会暂停一秒: +当运行示例 16-10 中的代码时,将看到如下输出,每一行都会暂停一秒: ``` Got: hi @@ -196,9 +191,9 @@ Got: thread ### 通过克隆发送者来创建多个生产者 -差不多在本部分的开头,我们提到了`mpsc`是 *multiple producer, single consumer* 的缩写。可以扩展列表 16-11 中的代码来创建都向同一接收者发送值的多个线程。这可以通过克隆通道的发送端在来做到,如列表 16-11 所示: +差不多在本部分的开头,我们提到了`mpsc`是 *multiple producer, single consumer* 的缩写。可以扩展示例 16-11 中的代码来创建都向同一接收者发送值的多个线程。这可以通过克隆通道的发送端在来做到,如示例 16-11 所示: -Filename: src/main.rs +文件名: src/main.rs ```rust # use std::thread; @@ -245,8 +240,7 @@ thread::spawn(move || { # } ``` -Listing 16-11: Sending multiple messages and pausing -between each one +示例 16-11: 发送多个消息,并在每次发送后暂停一段时间 这一次,在创建新线程之前,我们对通道的发送端调用了`clone`方法。这会给我们一个可以传递给第一个新建线程的发送端句柄。我们会将原始的通道发送端传递给第二个新建线程,这样每个线程将向通道的接收端发送不同的消息。 diff --git a/src/ch16-03-shared-state.md b/src/ch16-03-shared-state.md index 5ef8f80..653b463 100644 --- a/src/ch16-03-shared-state.md +++ b/src/ch16-03-shared-state.md @@ -28,9 +28,9 @@ ### `Mutex`的 API -让我们看看列表 16-12 中使用互斥器的例子,现在不涉及多线程: +让我们看看示例 16-12 中使用互斥器的例子,现在不涉及多线程: -Filename: src/main.rs +文件名: src/main.rs ```rust use std::sync::Mutex; @@ -47,23 +47,22 @@ fn main() { } ``` -Listing 16-12: Exploring the API of `Mutex` in a -single threaded context for simplicity +示例 16-12: 为简单,在一个单线程中探索 `Mutex` 的 API -像很多类型一样,我们使用关联函数 `new` 来创建一个 `Mutex`。使用`lock`方法获取锁,以访问互斥器中的数据。这个调用会阻塞,直到我们拥有锁为止。如果另一个线程拥有锁,并且那个线程 panic 了,则这个调用会失败。类似于列表 16-6 那样,我们暂时使用 `unwrap()` 进行错误处理,或者使用第九章中提及的更好的工具。 +像很多类型一样,我们使用关联函数 `new` 来创建一个 `Mutex`。使用`lock`方法获取锁,以访问互斥器中的数据。这个调用会阻塞,直到我们拥有锁为止。如果另一个线程拥有锁,并且那个线程 panic 了,则这个调用会失败。类似于示例 16-6 那样,我们暂时使用 `unwrap()` 进行错误处理,或者使用第九章中提及的更好的工具。 一旦获取了锁,就可以将返回值(在这里是`num`)作为一个数据的可变引用使用了。观察 Rust 类型系统如何保证使用值之前必须获取锁:`Mutex`并不是一个`i32`,所以**必须**获取锁才能使用这个`i32`值。我们是不会忘记这么做的,因为类型系统不允许。 -你也许会怀疑,`Mutex`是一个智能指针?是的!更准确的说,`lock`调用返回一个叫做`MutexGuard`的智能指针。类似我们在第十五章见过的智能指针,它实现了`Deref`来指向其内部数据。另外`MutexGuard`有一个用来释放锁的`Drop`实现。这样就不会忘记释放锁了。这在`MutexGuard`离开作用域时会自动发生,例如它发生于列表 16-12 中内部作用域的结尾。接着可以打印出互斥器的值并发现能够将其内部的`i32`改为 6。 +你也许会怀疑,`Mutex`是一个智能指针?是的!更准确的说,`lock`调用返回一个叫做`MutexGuard`的智能指针。类似我们在第十五章见过的智能指针,它实现了`Deref`来指向其内部数据。另外`MutexGuard`有一个用来释放锁的`Drop`实现。这样就不会忘记释放锁了。这在`MutexGuard`离开作用域时会自动发生,例如它发生于示例 16-12 中内部作用域的结尾。接着可以打印出互斥器的值并发现能够将其内部的`i32`改为 6。 #### 在线程间共享`Mutex` 现在让我们尝试使用`Mutex`在多个线程间共享值。我们将启动十个线程,并在各个线程中对同一个计数器值加一,这样计数器将从 0 变为 10。注意,接下来的几个例子会出现编译错误,而我们将通过这些错误来学习如何使用 -`Mutex`,以及 Rust 又是如何辅助我们以确保正确。列表 16-13 是最开始的例子: +`Mutex`,以及 Rust 又是如何辅助我们以确保正确。示例 16-13 是最开始的例子: -Filename: src/main.rs +文件名: src/main.rs ```rust,ignore use std::sync::Mutex; @@ -90,12 +89,11 @@ fn main() { } ``` -Listing 16-13: The start of a program having 10 threads -each increment a counter guarded by a `Mutex` +示例 16-13: 程序启动了 10 个线程,每个线程都通过 `Mutex` 来增加计数器的值 -这里创建了一个 `counter` 变量来存放内含 `i32` 的 `Mutex`,类似列表 16-12 那样。接下来使用 range 创建了 10 个线程。使用了 `thread::spawn` 并对所有线程使用了相同的闭包:他们每一个都将调用 `lock` 方法来获取 `Mutex` 上的锁,接着将互斥器中的值加一。当一个线程结束执行,`num` 会离开闭包作用域并释放锁,这样另一个线程就可以获取它了。 +这里创建了一个 `counter` 变量来存放内含 `i32` 的 `Mutex`,类似示例 16-12 那样。接下来使用 range 创建了 10 个线程。使用了 `thread::spawn` 并对所有线程使用了相同的闭包:他们每一个都将调用 `lock` 方法来获取 `Mutex` 上的锁,接着将互斥器中的值加一。当一个线程结束执行,`num` 会离开闭包作用域并释放锁,这样另一个线程就可以获取它了。 -在主线程中,我们像列表 16-2 那样收集了所有的 join 句柄,调用它们的 `join` 方法来确保所有线程都会结束。之后,主线程会获取锁并打印出程序的结果。 +在主线程中,我们像示例 16-2 那样收集了所有的 join 句柄,调用它们的 `join` 方法来确保所有线程都会结束。之后,主线程会获取锁并打印出程序的结果。 之前提示过这个例子不能编译,让我们看看为什么! @@ -114,7 +112,7 @@ referenced variables), use the `move` keyword, as shown: | let handle = thread::spawn(move || { ``` -这类似于列表 16-5 中解决了的问题。考虑到启动了多个线程,Rust 无法知道这些线程会运行多久,而在每一个线程尝试借用 `counter` 时它是否仍然有效。帮助信息提醒了我们如何解决它:可以使用 `move` 来给予每个线程其所有权。尝试在闭包上做一点改动: +这类似于示例 16-5 中解决了的问题。考虑到启动了多个线程,Rust 无法知道这些线程会运行多久,而在每一个线程尝试借用 `counter` 时它是否仍然有效。帮助信息提醒了我们如何解决它:可以使用 `move` 来给予每个线程其所有权。尝试在闭包上做一点改动: ```rust,ignore thread::spawn(move || { @@ -149,9 +147,9 @@ error[E0382]: use of moved value: `counter` 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 let handle = thread::spawn(move || { @@ -203,9 +201,9 @@ error: aborting due to 2 previous errors #### 多线程和多所有权 -在第十五章中,我们通过使用智能指针 `Rc` 来创建引用计数的值,以便拥有多所有权。同时第十五章提到了 `Rc` 只能在单线程环境中使用,不过还是在这里试用 `Rc` 看看会发生什么。列表 16-14 将 `Mutex` 装进了 `Rc` 中,并在移入线程之前克隆了 `Rc`。再用循环来创建线程,保留闭包中的 `move` 关键字: +在第十五章中,我们通过使用智能指针 `Rc` 来创建引用计数的值,以便拥有多所有权。同时第十五章提到了 `Rc` 只能在单线程环境中使用,不过还是在这里试用 `Rc` 看看会发生什么。示例 16-14 将 `Mutex` 装进了 `Rc` 中,并在移入线程之前克隆了 `Rc`。再用循环来创建线程,保留闭包中的 `move` 关键字: -Filename: src/main.rs +文件名: src/main.rs ```rust use std::rc::Rc; @@ -234,8 +232,7 @@ fn main() { } ``` -Listing 16-14: Attempting to use `Rc` to allow -multiple threads to own the `Mutex` +示例 16-14: 尝试使用 `Rc` 来允许多个线程拥有 `Mutex` 再一次编译并...出现了不同的错误!编译器真是教会了我们很多! @@ -266,9 +263,9 @@ std::marker::Send` is not satisfied 为什么不是所有的原始类型都是原子性的?为什么不是所有标准库中的类型都默认使用`Arc`实现?线程安全带来性能惩罚,我们希望只在必要时才为此买单。如果只是在单线程中对值进行操作,原子性提供的保证并无必要,代码可以因此运行的更快。 -回到之前的例子:`Arc`和`Rc`除了`Arc`内部的原子性之外没有区别。其 API 也相同,所以可以修改`use`行和`new`调用。列表 16-15 中的代码最终可以编译和运行: +回到之前的例子:`Arc`和`Rc`除了`Arc`内部的原子性之外没有区别。其 API 也相同,所以可以修改`use`行和`new`调用。示例 16-15 中的代码最终可以编译和运行: -Filename: src/main.rs +文件名: src/main.rs ```rust use std::sync::{Mutex, Arc}; @@ -296,8 +293,7 @@ fn main() { } ``` -Listing 16-15: Using an `Arc` to wrap the `Mutex` -to be able to share ownership across multiple threads +示例 16-15: 使用 `Arc` 包装一个 `Mutex` 能够实现在多线程之间共享所有权 这会打印出: