Merge branch 'c-16' into typo

pull/373/head
orzyyyy 5 years ago
commit b6f9350992

@ -21,6 +21,8 @@ Rust 的核心功能(之一)是 **所有权***ownership*)。虽然该
> 栈中的所有数据都必须占用已知且固定的大小。在编译时大小未知或大小可能变化的数据,要改为存储在堆上。堆是缺乏组织的:当向堆放入数据时,你要请求一定大小的空间。操作系统在堆的某处找到一块足够大的空位,把它标记为已使用,并返回一个表示该位置地址的 **指针***pointer*)。这个过程称作 **在堆上分配内存***allocating on the heap*),有时简称为 “分配”allocating。将数据推入栈中并不被认为是分配。因为指针的大小是已知并且固定的你可以将指针存储在栈上不过当需要实际数据时必须访问指针。 > 栈中的所有数据都必须占用已知且固定的大小。在编译时大小未知或大小可能变化的数据,要改为存储在堆上。堆是缺乏组织的:当向堆放入数据时,你要请求一定大小的空间。操作系统在堆的某处找到一块足够大的空位,把它标记为已使用,并返回一个表示该位置地址的 **指针***pointer*)。这个过程称作 **在堆上分配内存***allocating on the heap*),有时简称为 “分配”allocating。将数据推入栈中并不被认为是分配。因为指针的大小是已知并且固定的你可以将指针存储在栈上不过当需要实际数据时必须访问指针。
> >
> 想象一下去餐馆就座吃饭。当进入时,你说明有几个人,餐馆员工会找到一个够大的空桌子并领你们过去。如果有人来迟了,他们也可以通过询问来找到你们坐在哪。 > 想象一下去餐馆就座吃饭。当进入时,你说明有几个人,餐馆员工会找到一个够大的空桌子并领你们过去。如果有人来迟了,他们也可以通过询问来找到你们坐在哪。
>
> 入栈比在堆上分配内存要快,因为(入栈时)操作系统无需为存储新数据去搜索内存空间;其位置总是在栈顶。相比之下,在堆上分配内存则需要更多的工作,这是因为操作系统必须首先找到一块足够存放数据的内存空间,并接着做一些记录为下一次分配做准备。
> >
> 访问堆上的数据比访问栈上的数据慢,因为必须通过指针来访问。现代处理器在内存中跳转越少就越快(缓存)。继续类比,假设有一个服务员在餐厅里处理多个桌子的点菜。在一个桌子报完所有菜后再移动到下一个桌子是最有效率的。从桌子 A 听一个菜,接着桌子 B 听一个菜,然后再桌子 A然后再桌子 B 这样的流程会更加缓慢。出于同样原因,处理器在处理的数据彼此较近的时候(比如在栈上)比较远的时候(比如可能在堆上)能更好的工作。在堆上分配大量的空间也可能消耗时间。 > 访问堆上的数据比访问栈上的数据慢,因为必须通过指针来访问。现代处理器在内存中跳转越少就越快(缓存)。继续类比,假设有一个服务员在餐厅里处理多个桌子的点菜。在一个桌子报完所有菜后再移动到下一个桌子是最有效率的。从桌子 A 听一个菜,接着桌子 B 听一个菜,然后再桌子 A然后再桌子 B 这样的流程会更加缓慢。出于同样原因,处理器在处理的数据彼此较近的时候(比如在栈上)比较远的时候(比如可能在堆上)能更好的工作。在堆上分配大量的空间也可能消耗时间。
> >

@ -1,12 +1,11 @@
# 无畏并发 # 无畏并发
> [ch16-00-concurrency.md](https://github.com/rust-lang/book/blob/master/src/ch16-00-concurrency.md) > [ch16-00-concurrency.md](https://github.com/rust-lang/book/blob/master/src/ch16-00-concurrency.md) > <br>
> <br>
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f > commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
安全并高效的处理并发编程是 Rust 的另一个主要目标。**并发编程***Concurrent programming*),代表程序的不同部分相互独立的执行,而 **并行编程***parallel programming*代表程序不同部分于同时执行这两个概念随着计算机越来越多的利用多处理器的优势时显得愈发重要。由于历史原因在此类上下文中编程一直是困难且容易出错的Rust 希望能改变这一点。 安全并高效的处理并发编程是 Rust 的另一个主要目标。**并发编程**_Concurrent programming_代表程序的不同部分相互独立的执行**并行编程**_parallel programming_代表程序不同部分于同时执行这两个概念随着计算机越来越多的利用多处理器的优势时显得愈发重要。由于历史原因在此类上下文中编程一直是困难且容易出错的Rust 希望能改变这一点。
起初Rust 团队认为确保内存安全和防止并发问题是两个分别需要不同方法应对的挑战。随着时间的推移,团队发现所有权和类型系统是一系列解决内存安全 **和** 并发问题的强有力的工具!通过利用所有权和类型检查,在 Rust 中很多并发错误都是 **编译时** 错误,而非运行时错误。因此,相比花费大量时间尝试重现运行时并发 bug 出现的特定情况Rust 会拒绝编译不正确的代码并提供解释问题的错误信息。因此,你可以在开发时修复代码,而不是在部署到生产环境后修复代码。我们给 Rust 的这一部分起了一个绰号 **无畏并发***fearless concurrency*)。无畏并发令你的代码免于出现诡异的 bug 并可以轻松重构且无需担心会引入新的 bug。 起初Rust 团队认为确保内存安全和防止并发问题是两个分别需要不同方法应对的挑战。随着时间的推移,团队发现所有权和类型系统是一系列解决内存安全 **和** 并发问题的强有力的工具!通过利用所有权和类型检查,在 Rust 中很多并发错误都是 **编译时** 错误,而非运行时错误。因此,相比花费大量时间尝试重现运行时并发 bug 出现的特定情况Rust 会拒绝编译不正确的代码并提供解释问题的错误信息。因此,你可以在开发时修复代码,而不是在部署到生产环境后修复代码。我们给 Rust 的这一部分起了一个绰号 **无畏并发**_fearless concurrency_)。无畏并发令你的代码免于出现诡异的 bug 并可以轻松重构且无需担心会引入新的 bug。
> 注意:出于简洁的考虑,我们将很多问题归类为 **并发**,而不是更准确的区分 **并发和(或)并行**。如果这是一本专注于并发和/或并行的书,我们肯定会更加精确的。对于本章,当我们谈到 **并发** 时,请自行脑内替换为 **并发和(或)并行**。 > 注意:出于简洁的考虑,我们将很多问题归类为 **并发**,而不是更准确的区分 **并发和(或)并行**。如果这是一本专注于并发和/或并行的书,我们肯定会更加精确的。对于本章,当我们谈到 **并发** 时,请自行脑内替换为 **并发和(或)并行**。
@ -14,7 +13,7 @@
如下是本章将要涉及到的内容: 如下是本章将要涉及到的内容:
* 如何创建线程来同时运行多段代码。 - 如何创建线程来同时运行多段代码。
* **消息传递***Message passing*并发其中通道channel被用来在线程间传递消息。 - **消息传递**_Message passing_并发其中通道channel被用来在线程间传递消息。
* **共享状态***Shared state*)并发,其中多个线程可以访问同一片数据。 - **共享状态**_Shared state_)并发,其中多个线程可以访问同一片数据。
* `Sync``Send` trait他们允许 Rust 的并发保证能被扩展到用户定义的和标准库中提供的类型中。 - `Sync``Send` trait他们允许 Rust 的并发保证能被扩展到用户定义的和标准库中提供的类型中。

@ -1,34 +1,33 @@
## 使用线程同时运行代码 ## 使用线程同时运行代码
> [ch16-01-threads.md](https://github.com/rust-lang/book/blob/master/src/ch16-01-threads.md) > [ch16-01-threads.md](https://github.com/rust-lang/book/blob/master/src/ch16-01-threads.md) > <br>
> <br>
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f > commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
在大部分现代操作系统中,执行中程序的代码运行于一个 **进程***process*)中,操作系统则负责管理多个进程。在程序内部,也可以拥有多个同时运行的独立部分。这个运行这些独立部分的功能被称为 **线程***threads*)。 在大部分现代操作系统中,执行中程序的代码运行于一个 **进程**_process_)中,操作系统则负责管理多个进程。在程序内部,也可以拥有多个同时运行的独立部分。这个运行这些独立部分的功能被称为 **线程**_threads_)。
将程序中的计算拆分进多个线程可以改善性能,因为程序可以同时进行多个任务,不过这也会增加复杂性。因为线程是同时运行的,所以无法预先保证不同线程中的代码的执行顺序。这会导致诸如此类的问题: 将程序中的计算拆分进多个线程可以改善性能,因为程序可以同时进行多个任务,不过这也会增加复杂性。因为线程是同时运行的,所以无法预先保证不同线程中的代码的执行顺序。这会导致诸如此类的问题:
* 竞争状态Race conditions多个线程以不一致的顺序访问数据或资源 - 竞争状态Race conditions多个线程以不一致的顺序访问数据或资源
* 死锁Deadlocks两个线程相互等待对方停止使用其所拥有的资源这会阻止它们继续运行 - 死锁Deadlocks两个线程相互等待对方停止使用其所拥有的资源这会阻止它们继续运行
* 只会发生在特定情况且难以稳定重现和修复的 bug - 只会发生在特定情况且难以稳定重现和修复的 bug
Rust 尝试缓和使用线程的负面影响。不过在多线程上下文中编程仍需格外小心,同时其所要求的代码结构也不同于运行于单线程的程序。 Rust 尝试缓和使用线程的负面影响。不过在多线程上下文中编程仍需格外小心,同时其所要求的代码结构也不同于运行于单线程的程序。
编程语言有一些不同的方法来实现线程。很多操作系统提供了创建新线程的 API。这种由编程语言调用操作系统 API 创建线程的模模型有时被称为 *1:1*,一个 OS 线程对应一个语言线程。 编程语言有一些不同的方法来实现线程。很多操作系统提供了创建新线程的 API。这种由编程语言调用操作系统 API 创建线程的模型有时被称为 _1:1_,一个 OS 线程对应一个语言线程。
很多编程语言提供了自己特殊的线程实现。编程语言提供的线程被称为 **绿色***green*)线程,使用绿色线程的语言会在不同数量的 OS 线程的上下文中执行它们。为此,绿色线程模式被称为 *M:N* 模型:`M` 个绿色线程对应 `N` 个 OS 线程,这里 `M``N` 不必相同。 很多编程语言提供了自己特殊的线程实现。编程语言提供的线程被称为 **绿色**_green_)线程,使用绿色线程的语言会在不同数量的 OS 线程的上下文中执行它们。为此,绿色线程模式被称为 _M:N_ 模型:`M` 个绿色线程对应 `N` 个 OS 线程,这里 `M``N` 不必相同。
每一个模型都有其优势和取舍。对于 Rust 来说最重要的取舍是运行时支持。**运行时***Runtime*)是一个令人迷惑的概念,其在不同上下文中可能有不同的含义。 每一个模型都有其优势和取舍。对于 Rust 来说最重要的取舍是运行时支持。**运行时**_Runtime_)是一个令人迷惑的概念,其在不同上下文中可能有不同的含义。
在当前上下文中,**运行时** 代表二进制文件中包含的由语言自身提供的代码。这些代码根据语言的不同可大可小,不过任何非汇编语言都会有一定数量的运行时代码。为此,通常人们说一个语言 “没有运行时”,一般意味着 “小运行时”。更小的运行时拥有更少的功能不过其优势在于更小的二进制输出,这使其易于在更多上下文中与其他语言相结合。虽然很多语言觉得增加运行时来换取更多功能没有什么问题,但是 Rust 需要做到几乎没有运行时,同时为了保持高性能必需能够调用 C 语言,这点也是不能妥协的。 在当前上下文中,**运行时** 代表二进制文件中包含的由语言自身提供的代码。这些代码根据语言的不同可大可小,不过任何非汇编语言都会有一定数量的运行时代码。为此,通常人们说一个语言 “没有运行时”,一般意味着 “小运行时”。更小的运行时拥有更少的功能不过其优势在于更小的二进制输出,这使其易于在更多上下文中与其他语言相结合。虽然很多语言觉得增加运行时来换取更多功能没有什么问题,但是 Rust 需要做到几乎没有运行时,同时为了保持高性能必需能够调用 C 语言,这点也是不能妥协的。
绿色线程的 M:N 模型需要更大的语言运行时来管理这些线程。为此Rust 标准库只提供了 1:1 线程模型实现。因为 Rust 是足够底层的语言,有一些 crate 实现了 M:N 线程模型,如果你宁愿牺牲性能来换取例如更好的线程运行控制和更低的上下文切换成本 绿色线程的 M:N 模型需要更大的语言运行时来管理这些线程。因此Rust 标准库只提供了 1:1 线程模型实现。由于 Rust 是较为底层的语言,如果你愿意牺牲性能来换取的抽象,以获得对线程运行更精细的控制及更低的上下文切换成本,你可以使用实现了 M:N 线程模型的 crate
现在我们明白了 Rust 中的线程是如何定义的,让我们开始探索如何使用标准库提供的线程相关的 API 吧。 现在我们明白了 Rust 中的线程是如何定义的,让我们开始探索如何使用标准库提供的线程相关的 API 吧。
### 使用 `spawn` 创建新线程 ### 使用 `spawn` 创建新线程
为了创建一个新线程,需要调用 `thread::spawn` 函数并传递一个闭包(第十三章学习了闭包),其包含希望在新线程运行的代码。示例 16-1 中的例子在主线程打印了一些文本而另一些文本则由新线程打印: 为了创建一个新线程,需要调用 `thread::spawn` 函数并传递一个闭包(第十三章学习了闭包),并在包含希望在新线程运行的代码。示例 16-1 中的例子在主线程打印了一些文本而另一些文本则由新线程打印:
<span class="filename">文件名: src/main.rs</span> <span class="filename">文件名: src/main.rs</span>
@ -67,7 +66,7 @@ hi number 4 from the spawned thread!
hi number 5 from the spawned thread! hi number 5 from the spawned thread!
``` ```
`thread::sleep` 调用强制线程停止执行一小段时间,这会允许其他不同的线程运行。这些线程可能会轮流运行,不过并不保证如此:这依赖操作系统如何调度线程。在这里,主线程首先打印,即便新创建线程的打印语句位于程序的开头甚至即便我们告诉新建的线程打印直到 `i` 等于 9 ,它在主线程结束之前也只打印到了 5。 `thread::sleep` 调用强制线程停止执行一小段时间,这会允许其他不同的线程运行。这些线程可能会轮流运行,不过并不保证如此:这依赖操作系统如何调度线程。在这里,主线程首先打印,即便新创建线程的打印语句位于程序的开头甚至即便我们告诉新建的线程打印直到 `i` 等于 9 ,它在主线程结束之前也只打印到了 5。
如果运行代码只看到了主线程的输出,或没有出现重叠打印的现象,尝试增加 range 的数值来增加操作系统切换线程的机会。 如果运行代码只看到了主线程的输出,或没有出现重叠打印的现象,尝试增加 range 的数值来增加操作系统切换线程的机会。
@ -102,7 +101,7 @@ fn main() {
<span class="caption">示例 16-2: 从 `thread::spawn` 保存一个 `JoinHandle` 以确保该线程能够运行至结束</span> <span class="caption">示例 16-2: 从 `thread::spawn` 保存一个 `JoinHandle` 以确保该线程能够运行至结束</span>
通过调用 handle `join` 会阻塞当前线程直到 handle 所代表的线程结束。**阻塞***Blocking* 线程意味着阻止该线程执行工作或退出。因为我们将 `join` 调用放在了主线程的 `for` 循环之后,运行示例 16-2 应该会产生类似这样的输出: 通过调用 handle 的 `join` 会阻塞当前线程直到 handle 所代表的线程结束。**阻塞**_Blocking_ 线程意味着阻止该线程执行工作或退出。因为我们将 `join` 调用放在了主线程的 `for` 循环之后,运行示例 16-2 应该会产生类似这样的输出:
```text ```text
hi number 1 from the main thread! hi number 1 from the main thread!

@ -1,15 +1,14 @@
## 使用消息传递在线程间传送数据 ## 使用消息传递在线程间传送数据
> [ch16-02-message-passing.md](https://github.com/rust-lang/book/blob/master/src/ch16-02-message-passing.md) > [ch16-02-message-passing.md](https://github.com/rust-lang/book/blob/master/src/ch16-02-message-passing.md) > <br>
> <br>
> commit 26565efc3f62d9dacb7c2c6d0f5974360e459493 > commit 26565efc3f62d9dacb7c2c6d0f5974360e459493
一个日益流行的确保安全并发的方式是 **消息传递***message passing*),这里线程或 actor 通过发送包含数据的消息来相互沟通。这个思想来源于 [Go 编程语言文档中](http://golang.org/doc/effective_go.html) 的口号“不要共享内存来通讯而是要通讯来共享内存。”“Do not communicate by 一个日益流行的确保安全并发的方式是 **消息传递**_message passing_),这里线程或 actor 通过发送包含数据的消息来相互沟通。这个思想来源于 [Go 编程语言文档中](http://golang.org/doc/effective_go.html) 的口号“不要共享内存来通讯而是要通讯来共享内存。”“Do not communicate by
sharing memory; instead, share memory by communicating.”) sharing memory; instead, share memory by communicating.”)
Rust 中一个实现消息传递并发的主要工具是 **通道***channel*),一个 Rust 标准库提供了其实现的编程概念。你可以将其想象为一个水流的通道,比如河流或小溪。如果你将诸如橡皮鸭或小船之类的东西放入其中,它们会顺流而下到达下游。 Rust 中一个实现消息传递并发的主要工具是 **通道**_channel_),一个 Rust 标准库提供了其实现的编程概念。你可以将其想象为一个水流的通道,比如河流或小溪。如果你将诸如橡皮鸭或小船之类的东西放入其中,它们会顺流而下到达下游。
编程中的通道有两部分组成一个发送者transmitter和一个接收者receiver。发送者一端位于上游位置在这里可以将橡皮鸭放入河中接收者部分则位于下游橡皮鸭最终会漂流至此。代码中的一部分调用发送者的方法以及希望发送的数据另一部分则检查接收端收到到达的消息。当发送者或接收者任一被丢弃时可以认为通道被 **关闭***closed*)了 编程中的通道有两部分组成一个发送者transmitter和一个接收者receiver。发送者一端位于上游位置在这里可以将橡皮鸭放入河中接收者部分则位于下游橡皮鸭最终会漂流至此。代码中的一部分调用发送者的方法以及希望发送的数据另一部分则检查接收端收到到达的消息。当发送者或接收者任一被丢弃时可以认为通道被 **关闭**_closed_)了
这里,我们将开发一个程序,它会在一个线程生成值向通道发送,而在另一个线程会接收值并打印出来。这里会通过通道在线程间发送简单值来演示这个功能。一旦你熟悉了这项技术,就能使用通道来实现聊天系统或利用很多线程进行分布式计算并将部分计算结果发送给一个线程进行聚合。 这里,我们将开发一个程序,它会在一个线程生成值向通道发送,而在另一个线程会接收值并打印出来。这里会通过通道在线程间发送简单值来演示这个功能。一旦你熟悉了这项技术,就能使用通道来实现聊天系统或利用很多线程进行分布式计算并将部分计算结果发送给一个线程进行聚合。
@ -27,9 +26,9 @@ fn main() {
<span class="caption">示例 16-6: 创建一个通道,并将其两端赋值给 `tx``rx`</span> <span class="caption">示例 16-6: 创建一个通道,并将其两端赋值给 `tx``rx`</span>
这里使用 `mpsc::channel` 函数创建一个新的通道;`mpsc` 是 **多个生产者,单个消费者***multiple producer, single consumer*的缩写。简而言之Rust 标准库实现通道的方式意味着一个通道可以有多个产生值的 **发送***sending*)端,但只能有一个消费这些值的 **接收***receiving*)端。想象一下多条小河小溪最终汇聚成大河:所有通过这些小河发出的东西最后都会来到大河的下游。目前我们以单个生产者开始,但是当示例可以工作后会增加多个生产者。 这里使用 `mpsc::channel` 函数创建一个新的通道;`mpsc` 是 **多个生产者,单个消费者**_multiple producer, single consumer_的缩写。简而言之Rust 标准库实现通道的方式意味着一个通道可以有多个产生值的 **发送**_sending_但只能有一个消费这些值的 **接收**_receiving_)端。想象一下多条小河小溪最终汇聚成大河:所有通过这些小河发出的东西最后都会来到大河的下游。目前我们以单个生产者开始,但是当示例可以工作后会增加多个生产者。
`mpsc::channel` 函数返回一个元组:第一个元素是发送端,而第二个元素是接收端。由于历史原因,`tx` 和 `rx` 通常作为 **发送者***transmitter*)和 **接收者***receiver*)的缩写,所以这就是我们将用来绑定这两端变量的名字。这里使用了一个 `let` 语句和模式来解构了此元组;第十八章会讨论 `let` 语句中的模式和解构。如此使用 `let` 语句是一个方便提取 `mpsc::channel` 返回的元组中一部分的手段。 `mpsc::channel` 函数返回一个元组:第一个元素是发送端,而第二个元素是接收端。由于历史原因,`tx` 和 `rx` 通常作为 **发送者**_transmitter_**接收者**_receiver_)的缩写,所以这就是我们将用来绑定这两端变量的名字。这里使用了一个 `let` 语句和模式来解构了此元组;第十八章会讨论 `let` 语句中的模式和解构。如此使用 `let` 语句是一个方便提取 `mpsc::channel` 返回的元组中一部分的手段。
让我们将发送端移动到一个新建线程中并发送一个字符串,这样新建线程就可以和主线程通讯了,如示例 16-7 所示。这类似与在河的上游扔下一只橡皮鸭或从一个线程向另一个线程发送聊天信息: 让我们将发送端移动到一个新建线程中并发送一个字符串,这样新建线程就可以和主线程通讯了,如示例 16-7 所示。这类似与在河的上游扔下一只橡皮鸭或从一个线程向另一个线程发送聊天信息:
@ -53,7 +52,7 @@ fn main() {
这里再次使用 `thread::spawn` 来创建一个新线程并使用 `move``tx` 移动到闭包中这样新建线程就拥有 `tx` 了。新建线程需要拥有通道的发送端以便能向通道发送消息。 这里再次使用 `thread::spawn` 来创建一个新线程并使用 `move``tx` 移动到闭包中这样新建线程就拥有 `tx` 了。新建线程需要拥有通道的发送端以便能向通道发送消息。
通道的发送端有一个 `send` 方法用来获取需要放入通道的值。`send` 方法返回一个 `Result<T, E>` 类型,所以如果接收端已经被丢弃了,将没有发送值的目标,所以发送操作会返回错误。在这个例子中,出错的时候调用 `unwrap` 产生 panic。过对于一个真实程序需要合理处理它:回到第九章复习正确处理错误的策略。 通道的发送端有一个 `send` 方法用来获取需要放入通道的值。`send` 方法返回一个 `Result<T, E>` 类型,所以如果接收端已经被丢弃了,将没有发送值的目标,所以发送操作会返回错误。在这个例子中,出错的时候调用 `unwrap` 产生 panic。过对于一个真实程序需要合理处理它:回到第九章复习正确处理错误的策略。
在示例 16-8 中,我们在主线程中从通道的接收端获取值。这类似于在河的下游捞起橡皮鸭或接收聊天信息: 在示例 16-8 中,我们在主线程中从通道的接收端获取值。这类似于在河的下游捞起橡皮鸭或接收聊天信息:
@ -78,7 +77,7 @@ fn main() {
<span class="caption">示例 16-8: 在主线程中接收并打印内容 “hi”</span> <span class="caption">示例 16-8: 在主线程中接收并打印内容 “hi”</span>
通道的接收端有两个有用的方法:`recv` 和 `try_recv`。这里,我们使用了 `recv`,它是 *receive* 的缩写。这个方法会阻塞主线程执行直到从通道中接收一个值。一旦发送了一个值,`recv` 会在一个 `Result<T, E>` 中返回它。当通道发送端关闭,`recv` 会返回一个错误表明不会再有新的值到来了。 通道的接收端有两个有用的方法:`recv` 和 `try_recv`。这里,我们使用了 `recv`,它是 _receive_ 的缩写。这个方法会阻塞主线程执行直到从通道中接收一个值。一旦发送了一个值,`recv` 会在一个 `Result<T, E>` 中返回它。当通道发送端关闭,`recv` 会返回一个错误表明不会再有新的值到来了。
`try_recv` 不会阻塞,相反它立刻返回一个 `Result<T, E>``Ok` 值包含可用的信息,而 `Err` 值代表此时没有任何消息。如果线程在等待消息过程中还有其他工作时使用 `try_recv` 很有用:可以编写一个循环来频繁调用 `try_recv`,再有可用消息时进行处理,其余时候则处理一会其他工作直到再次检查。 `try_recv` 不会阻塞,相反它立刻返回一个 `Result<T, E>``Ok` 值包含可用的信息,而 `Err` 值代表此时没有任何消息。如果线程在等待消息过程中还有其他工作时使用 `try_recv` 很有用:可以编写一个循环来频繁调用 `try_recv`,再有可用消息时进行处理,其余时候则处理一会其他工作直到再次检查。
@ -188,7 +187,7 @@ Got: thread
### 通过克隆发送者来创建多个生产者 ### 通过克隆发送者来创建多个生产者
之前我们提到了`mpsc`是 *multiple producer, single consumer* 的缩写。可以运用 `mpsc` 来扩展示例 16-11 中的代码来以创建都向同一接收者发送值的多个线程。这可以通过克隆通道的发送端在来做到,如示例 16-11 所示: 之前我们提到了`mpsc`是 _multiple producer, single consumer_ 的缩写。可以运用 `mpsc` 来扩展示例 16-11 中的代码来以创建都向同一接收者发送值的多个线程。这可以通过克隆通道的发送端在来做到,如示例 16-11 所示:
<span class="filename">文件名: src/main.rs</span> <span class="filename">文件名: src/main.rs</span>

@ -1,7 +1,6 @@
## 共享状态并发 ## 共享状态并发
> [ch16-03-shared-state.md](https://github.com/rust-lang/book/blob/master/src/ch16-03-shared-state.md) > [ch16-03-shared-state.md](https://github.com/rust-lang/book/blob/master/src/ch16-03-shared-state.md) > <br>
> <br>
> commit ef072458f903775e91ea9e21356154bc57ee31da > commit ef072458f903775e91ea9e21356154bc57ee31da
虽然消息传递是一个很好的处理并发的方式,但并不是唯一一个。再一次思考一下 Go 编程语言文档中口号的这一部分“不要通过共享内存来通讯”“do not communicate by sharing memory.”): 虽然消息传递是一个很好的处理并发的方式,但并不是唯一一个。再一次思考一下 Go 编程语言文档中口号的这一部分“不要通过共享内存来通讯”“do not communicate by sharing memory.”):
@ -14,7 +13,7 @@
### 互斥器一次只允许一个线程访问数据 ### 互斥器一次只允许一个线程访问数据
**互斥器***mutex*)是 *mutual exclusion* 的缩写,也就是说,任意时刻,其只允许一个线程访问某些数据。为了访问互斥器中的数据,线程首先需要通过获取互斥器的 **锁***lock*)来表明其希望访问数据。锁是一个作为互斥器一部分的数据结构,它记录谁有数据的排他访问权。因此,我们描述互斥器为通过锁系统 **保护***guarding*)其数据。 **互斥器**_mutex__mutual exclusion_ 的缩写,也就是说,任意时刻,其只允许一个线程访问某些数据。为了访问互斥器中的数据,线程首先需要通过获取互斥器的 **锁**_lock_)来表明其希望访问数据。锁是一个作为互斥器一部分的数据结构,它记录谁有数据的排他访问权。因此,我们描述互斥器为通过锁系统 **保护**_guarding_)其数据。
互斥器以难以使用著称,因为你不得不记住: 互斥器以难以使用著称,因为你不得不记住:
@ -172,7 +171,7 @@ is not implemented for `std::rc::Rc<std::sync::Mutex<i32>>`
#### 原子引用计数 `Arc<T>` #### 原子引用计数 `Arc<T>`
所幸 `Arc<T>` **正是** 这么一个类似 `Rc<T>` 并可以安全的用于并发环境的类型。字母 “a” 代表 **原子性***atomic*),所以这是一个**原子引用计数***atomically reference counted*)类型。原子性是另一类这里还未涉及到的并发原语:请查看标准库中 `std::sync::atomic` 的文档来获取更多细节。其中的要点就是:原子性类型工作起来类似原始类型,不过可以安全的在线程间共享。 所幸 `Arc<T>` **正是** 这么一个类似 `Rc<T>` 并可以安全的用于并发环境的类型。字母 “a” 代表 **原子性**_atomic_所以这是一个**原子引用计数**_atomically reference counted_)类型。原子性是另一类这里还未涉及到的并发原语:请查看标准库中 `std::sync::atomic` 的文档来获取更多细节。其中的要点就是:原子性类型工作起来类似原始类型,不过可以安全的在线程间共享。
你可能会好奇为什么不是所有的原始类型都是原子性的?为什么不是所有标准库中的类型都默认使用 `Arc<T>` 实现?原因在于线程安全带有性能惩罚,我们希望只在必要时才为此买单。如果只是在单线程中对值进行操作,原子性提供的保证并无必要,代码可以因此运行的更快。 你可能会好奇为什么不是所有的原始类型都是原子性的?为什么不是所有标准库中的类型都默认使用 `Arc<T>` 实现?原因在于线程安全带有性能惩罚,我们希望只在必要时才为此买单。如果只是在单线程中对值进行操作,原子性提供的保证并无必要,代码可以因此运行的更快。
@ -220,6 +219,6 @@ Result: 10
你可能注意到了,因为 `counter` 是不可变的,不过可以获取其内部值的可变引用;这意味着 `Mutex<T>` 提供了内部可变性,就像 `Cell` 系列类型那样。正如第十五章中使用 `RefCell<T>` 可以改变 `Rc<T>` 中的内容那样,同样的可以使用 `Mutex<T>` 来改变 `Arc<T>` 中的内容。 你可能注意到了,因为 `counter` 是不可变的,不过可以获取其内部值的可变引用;这意味着 `Mutex<T>` 提供了内部可变性,就像 `Cell` 系列类型那样。正如第十五章中使用 `RefCell<T>` 可以改变 `Rc<T>` 中的内容那样,同样的可以使用 `Mutex<T>` 来改变 `Arc<T>` 中的内容。
另一个值得注意的细节是 Rust 不能避免使用 `Mutex<T>` 的全部逻辑错误。回忆一下第十五章使用 `Rc<T>` 就有造成引用循环的风险,这时两个 `Rc<T>` 值相互引用,造成内存泄露。同理,`Mutex<T>` 也有造成 **死锁***deadlock* 的风险。这发生于当一个操作需要锁住两个资源而两个线程各持一个锁,这会造成它们永远相互等待。如果你对这个主题感兴趣,尝试编写一个带有死锁的 Rust 程序,接着研究任何其他语言中使用互斥器的死锁规避策略并尝试在 Rust 中实现他们。标准库中 `Mutex<T>``MutexGuard` 的 API 文档会提供有用的信息。 另一个值得注意的细节是 Rust 不能避免使用 `Mutex<T>` 的全部逻辑错误。回忆一下第十五章使用 `Rc<T>` 就有造成引用循环的风险,这时两个 `Rc<T>` 值相互引用,造成内存泄露。同理,`Mutex<T>` 也有造成 **死锁**_deadlock_ 的风险。这发生于当一个操作需要锁住两个资源而两个线程各持一个锁,这会造成它们永远相互等待。如果你对这个主题感兴趣,尝试编写一个带有死锁的 Rust 程序,接着研究任何其他语言中使用互斥器的死锁规避策略并尝试在 Rust 中实现他们。标准库中 `Mutex<T>``MutexGuard` 的 API 文档会提供有用的信息。
接下来,为了丰富本章的内容,让我们讨论一下 `Send``Sync` trait 以及如何对自定义类型使用他们。 接下来,为了丰富本章的内容,让我们讨论一下 `Send``Sync` trait 以及如何对自定义类型使用他们。

@ -1,7 +1,6 @@
## 使用 `Sync``Send` trait 的可扩展并发 ## 使用 `Sync``Send` trait 的可扩展并发
> [ch16-04-extensible-concurrency-sync-and-send.md](https://github.com/rust-lang/book/blob/master/src/ch16-04-extensible-concurrency-sync-and-send.md) > [ch16-04-extensible-concurrency-sync-and-send.md](https://github.com/rust-lang/book/blob/master/src/ch16-04-extensible-concurrency-sync-and-send.md) > <br>
> <br>
> commit 426f3e4ec17e539ae9905ba559411169d303a031 > commit 426f3e4ec17e539ae9905ba559411169d303a031
Rust 的并发模型中一个有趣的方面是:语言本身对并发知之 **甚少**。我们之前讨论的几乎所有内容,都属于标准库,而不是语言本身的内容。由于不需要语言提供并发相关的基础设施,并发方案不受标准库或语言所限:我们可以编写自己的或使用别人编写的并发功能。 Rust 的并发模型中一个有趣的方面是:语言本身对并发知之 **甚少**。我们之前讨论的几乎所有内容,都属于标准库,而不是语言本身的内容。由于不需要语言提供并发相关的基础设施,并发方案不受标准库或语言所限:我们可以编写自己的或使用别人编写的并发功能。
@ -28,7 +27,7 @@ Rust 的并发模型中一个有趣的方面是:语言本身对并发知之 **
手动实现这些标记 trait 涉及到编写不安全的 Rust 代码,第十九章将会讲述具体的方法;当前重要的是,在创建新的由不是 `Send``Sync` 的部分构成的并发类型时需要多加小心,以确保维持其安全保证。[The Rustonomicon] 中有更多关于这些保证以及如何维持他们的信息。 手动实现这些标记 trait 涉及到编写不安全的 Rust 代码,第十九章将会讲述具体的方法;当前重要的是,在创建新的由不是 `Send``Sync` 的部分构成的并发类型时需要多加小心,以确保维持其安全保证。[The Rustonomicon] 中有更多关于这些保证以及如何维持他们的信息。
[The Rustonomicon]: https://doc.rust-lang.org/stable/nomicon/ [the rustonomicon]: https://doc.rust-lang.org/stable/nomicon/
## 总结 ## 总结
@ -40,5 +39,4 @@ Rust 提供了用于消息传递的通道,和像 `Mutex<T>` 和 `Arc<T>` 这
接下来,让我们讨论一下当 Rust 程序变得更大时,有哪些符合语言习惯的问题建模方法和结构化解决方案,以及 Rust 的风格是如何与面向对象编程Object Oriented Programming中那些你所熟悉的概念相联系的。 接下来,让我们讨论一下当 Rust 程序变得更大时,有哪些符合语言习惯的问题建模方法和结构化解决方案,以及 Rust 的风格是如何与面向对象编程Object Oriented Programming中那些你所熟悉的概念相联系的。
[sharing-a-mutext-between-multiple-threads]: [sharing-a-mutext-between-multiple-threads]: ch16-03-shared-state.html#sharing-a-mutext-between-multiple-threads
ch16-03-shared-state.html#sharing-a-mutext-between-multiple-threads

Loading…
Cancel
Save