chore: tweak indent

This commit is contained in:
orzyyyy 2020-01-30 11:15:29 +08:00
parent b6b423ba04
commit 4c2c6ea7e7
5 changed files with 31 additions and 37 deletions

View File

@ -1,12 +1,11 @@
# 无畏并发
> [ch16-00-concurrency.md](https://github.com/rust-lang/book/blob/master/src/ch16-00-concurrency.md)
> <br>
> [ch16-00-concurrency.md](https://github.com/rust-lang/book/blob/master/src/ch16-00-concurrency.md) > <br>
> 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被用来在线程间传递消息。
* **共享状态***Shared state*)并发,其中多个线程可以访问同一片数据。
* `Sync``Send` trait他们允许 Rust 的并发保证能被扩展到用户定义的和标准库中提供的类型中。
- 如何创建线程来同时运行多段代码。
- **消息传递**_Message passing_并发其中通道channel被用来在线程间传递消息。
- **共享状态**_Shared state_)并发,其中多个线程可以访问同一片数据。
- `Sync``Send` trait他们允许 Rust 的并发保证能被扩展到用户定义的和标准库中提供的类型中。

View File

@ -1,24 +1,23 @@
## 使用线程同时运行代码
> [ch16-01-threads.md](https://github.com/rust-lang/book/blob/master/src/ch16-01-threads.md)
> <br>
> [ch16-01-threads.md](https://github.com/rust-lang/book/blob/master/src/ch16-01-threads.md) > <br>
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
在大部分现代操作系统中,执行中程序的代码运行于一个 **进程***process*)中,操作系统则负责管理多个进程。在程序内部,也可以拥有多个同时运行的独立部分。这个运行这些独立部分的功能被称为 **线程***threads*)。
在大部分现代操作系统中,执行中程序的代码运行于一个 **进程**_process_)中,操作系统则负责管理多个进程。在程序内部,也可以拥有多个同时运行的独立部分。这个运行这些独立部分的功能被称为 **线程**_threads_)。
将程序中的计算拆分进多个线程可以改善性能,因为程序可以同时进行多个任务,不过这也会增加复杂性。因为线程是同时运行的,所以无法预先保证不同线程中的代码的执行顺序。这会导致诸如此类的问题:
* 竞争状态Race conditions多个线程以不一致的顺序访问数据或资源
* 死锁Deadlocks两个线程相互等待对方停止使用其所拥有的资源这会阻止它们继续运行
* 只会发生在特定情况且难以稳定重现和修复的 bug
- 竞争状态Race conditions多个线程以不一致的顺序访问数据或资源
- 死锁Deadlocks两个线程相互等待对方停止使用其所拥有的资源这会阻止它们继续运行
- 只会发生在特定情况且难以稳定重现和修复的 bug
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 语言,这点也是不能妥协的。
@ -102,7 +101,7 @@ fn main() {
<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
hi number 1 from the main thread!

View File

@ -1,15 +1,14 @@
## 使用消息传递在线程间传送数据
> [ch16-02-message-passing.md](https://github.com/rust-lang/book/blob/master/src/ch16-02-message-passing.md)
> <br>
> [ch16-02-message-passing.md](https://github.com/rust-lang/book/blob/master/src/ch16-02-message-passing.md) > <br>
> 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.”)
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>
这里使用 `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 所示。这类似与在河的上游扔下一只橡皮鸭或从一个线程向另一个线程发送聊天信息:
@ -78,7 +77,7 @@ fn main() {
<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`,再有可用消息时进行处理,其余时候则处理一会其他工作直到再次检查。
@ -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>

View File

@ -1,7 +1,6 @@
## 共享状态并发
> [ch16-03-shared-state.md](https://github.com/rust-lang/book/blob/master/src/ch16-03-shared-state.md)
> <br>
> [ch16-03-shared-state.md](https://github.com/rust-lang/book/blob/master/src/ch16-03-shared-state.md) > <br>
> commit ef072458f903775e91ea9e21356154bc57ee31da
虽然消息传递是一个很好的处理并发的方式,但并不是唯一一个。再一次思考一下 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>` **正是** 这么一个类似 `Rc<T>` 并可以安全的用于并发环境的类型。字母 “a” 代表 **原子性***atomic*),所以这是一个**原子引用计数***atomically reference counted*)类型。原子性是另一类这里还未涉及到的并发原语:请查看标准库中 `std::sync::atomic` 的文档来获取更多细节。其中的要点就是:原子性类型工作起来类似原始类型,不过可以安全的在线程间共享。
所幸 `Arc<T>` **正是** 这么一个类似 `Rc<T>` 并可以安全的用于并发环境的类型。字母 “a” 代表 **原子性**_atomic_所以这是一个**原子引用计数**_atomically reference counted_)类型。原子性是另一类这里还未涉及到的并发原语:请查看标准库中 `std::sync::atomic` 的文档来获取更多细节。其中的要点就是:原子性类型工作起来类似原始类型,不过可以安全的在线程间共享。
你可能会好奇为什么不是所有的原始类型都是原子性的?为什么不是所有标准库中的类型都默认使用 `Arc<T>` 实现?原因在于线程安全带有性能惩罚,我们希望只在必要时才为此买单。如果只是在单线程中对值进行操作,原子性提供的保证并无必要,代码可以因此运行的更快。
@ -220,6 +219,6 @@ Result: 10
你可能注意到了,因为 `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 以及如何对自定义类型使用他们。

View File

@ -1,7 +1,6 @@
## 使用 `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)
> <br>
> [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>
> commit 426f3e4ec17e539ae9905ba559411169d303a031
Rust 的并发模型中一个有趣的方面是:语言本身对并发知之 **甚少**。我们之前讨论的几乎所有内容,都属于标准库,而不是语言本身的内容。由于不需要语言提供并发相关的基础设施,并发方案不受标准库或语言所限:我们可以编写自己的或使用别人编写的并发功能。
@ -28,7 +27,7 @@ Rust 的并发模型中一个有趣的方面是:语言本身对并发知之 **
手动实现这些标记 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中那些你所熟悉的概念相联系的。
[sharing-a-mutext-between-multiple-threads]:
ch16-03-shared-state.html#sharing-a-mutext-between-multiple-threads
[sharing-a-mutext-between-multiple-threads]: ch16-03-shared-state.html#sharing-a-mutext-between-multiple-threads