mirror of
https://github.com/KaiserY/trpl-zh-cn
synced 2024-11-09 08:51:18 +08:00
commit
47919f6845
@ -3,7 +3,7 @@
|
||||
> [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。
|
||||
|
||||
@ -16,4 +16,4 @@
|
||||
- 如何创建线程来同时运行多段代码。
|
||||
- **消息传递**(_Message passing_)并发,其中通道(channel)被用来在线程间传递消息。
|
||||
- **共享状态**(_Shared state_)并发,其中多个线程可以访问同一片数据。
|
||||
- `Sync` 和 `Send` trait,他们允许 Rust 的并发保证能被扩展到用户定义的和标准库中提供的类型中。
|
||||
- `Sync` 和 `Send` trait,将 Rust 的并发保证扩展到用户定义的以及标准库提供的类型中。
|
||||
|
@ -3,7 +3,7 @@
|
||||
> [ch16-01-threads.md](https://github.com/rust-lang/book/blob/master/src/ch16-01-threads.md) > <br>
|
||||
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
||||
|
||||
在大部分现代操作系统中,执行中程序的代码运行于一个 **进程**(_process_)中,操作系统则负责管理多个进程。在程序内部,也可以拥有多个同时运行的独立部分。这个运行这些独立部分的功能被称为 **线程**(_threads_)。
|
||||
在大部分现代操作系统中,执行中程序的代码在一个 **进程**(_process_)中运行,操作系统则负责管理多个进程。在程序内部,也可以拥有多个同时运行的独立部分。这个运行这些独立部分的功能被称为 **线程**(_threads_)。
|
||||
|
||||
将程序中的计算拆分进多个线程可以改善性能,因为程序可以同时进行多个任务,不过这也会增加复杂性。因为线程是同时运行的,所以无法预先保证不同线程中的代码的执行顺序。这会导致诸如此类的问题:
|
||||
|
||||
@ -170,7 +170,7 @@ hi number 4 from the main thread!
|
||||
|
||||
`move` 闭包,我们曾在第十三章简要的提到过,其经常与 `thread::spawn` 一起使用,因为它允许我们在一个线程中使用另一个线程的数据。
|
||||
|
||||
第十三章讲到如果可以在参数列表前使用 `move` 关键字强制闭包获取其使用的环境值的所有权。这个技巧在将闭包传递给新线程以便将数据移动到新线程中时最为实用。
|
||||
在第十三章中,我们讲到可以在参数列表前使用 `move` 关键字强制闭包获取其使用的环境值的所有权。这个技巧在创建新线程将值的所有权从一个线程移动到另一个线程时最为实用。
|
||||
|
||||
注意示例 16-1 中传递给 `thread::spawn` 的闭包并没有任何参数:并没有在新建线程代码中使用任何主线程的数据。为了在新建线程中使用来自于主线程的数据,需要新建线程的闭包获取它需要的值。示例 16-3 展示了一个尝试在主线程中创建一个 vector 并用于新建线程的例子,不过这么写还不能工作,如下所示:
|
||||
|
||||
|
@ -3,14 +3,13 @@
|
||||
> [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
|
||||
sharing memory; instead, share memory by communicating.”)
|
||||
一个日益流行的确保安全并发的方式是 **消息传递**(_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 标准库提供了其实现的编程概念。你可以将其想象为一个水流的通道,比如河流或小溪。如果你将诸如橡皮鸭或小船之类的东西放入其中,它们会顺流而下到达下游。
|
||||
|
||||
编程中的通道有两部分组成,一个发送者(transmitter)和一个接收者(receiver)。发送者一端位于上游位置,在这里可以将橡皮鸭放入河中,接收者部分则位于下游,橡皮鸭最终会漂流至此。代码中的一部分调用发送者的方法以及希望发送的数据,另一部分则检查接收端收到到达的消息。当发送者或接收者任一被丢弃时可以认为通道被 **关闭**(_closed_)了
|
||||
编程中的通道有两部分组成,一个发送者(transmitter)和一个接收者(receiver)。发送者位于上游位置,在这里可以将橡皮鸭放入河中,接收者部分则位于下游,橡皮鸭最终会漂流至此。代码中的一部分调用发送者的方法以及希望发送的数据,另一部分则检查接收端收到的消息。当发送者或接收者任一被丢弃时可以认为通道被 **关闭**(_closed_)了。
|
||||
|
||||
这里,我们将开发一个程序,它会在一个线程生成值向通道发送,而在另一个线程会接收值并打印出来。这里会通过通道在线程间发送简单值来演示这个功能。一旦你熟悉了这项技术,就能使用通道来实现聊天系统或利用很多线程进行分布式计算并将部分计算结果发送给一个线程进行聚合。
|
||||
这里,我们将开发一个程序,它会在一个线程生成值向通道发送,而在另一个线程会接收值并打印出来。这里会通过通道在线程间发送简单值来演示这个功能。一旦你熟悉了这项技术,就能使用通道来实现聊天系统,或利用很多线程进行分布式计算并将部分计算结果发送给一个线程进行聚合。
|
||||
|
||||
首先,在示例 16-6 中,创建了一个通道但没有做任何事。注意这还不能编译,因为 Rust 不知道我们想要在通道中发送什么类型:
|
||||
|
||||
@ -30,7 +29,7 @@ fn main() {
|
||||
|
||||
`mpsc::channel` 函数返回一个元组:第一个元素是发送端,而第二个元素是接收端。由于历史原因,`tx` 和 `rx` 通常作为 **发送者**(_transmitter_)和 **接收者**(_receiver_)的缩写,所以这就是我们将用来绑定这两端变量的名字。这里使用了一个 `let` 语句和模式来解构了此元组;第十八章会讨论 `let` 语句中的模式和解构。如此使用 `let` 语句是一个方便提取 `mpsc::channel` 返回的元组中一部分的手段。
|
||||
|
||||
让我们将发送端移动到一个新建线程中并发送一个字符串,这样新建线程就可以和主线程通讯了,如示例 16-7 所示。这类似与在河的上游扔下一只橡皮鸭或从一个线程向另一个线程发送聊天信息:
|
||||
让我们将发送端移动到一个新建线程中并发送一个字符串,这样新建线程就可以和主线程通讯了,如示例 16-7 所示。这类似于在河的上游扔下一只橡皮鸭或从一个线程向另一个线程发送聊天信息:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -79,7 +78,7 @@ fn main() {
|
||||
|
||||
通道的接收端有两个有用的方法:`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`,在有可用消息时进行处理,其余时候则处理一会其他工作直到再次检查。
|
||||
|
||||
出于简单的考虑,这个例子使用了 `recv`;主线程中除了等待消息之外没有任何其他工作,所以阻塞主线程是合适的。
|
||||
|
||||
@ -93,7 +92,7 @@ Got: hi
|
||||
|
||||
### 通道与所有权转移
|
||||
|
||||
所有权规则在消息传递中扮演了重要角色,其有助于我们编写安全的并发代码。在并发编程中避免错误是在整个 Rust 程序中必须思考所有权所换来的一大优势。现在让我们做一个试验来看看通道与所有权如何一同协作以避免产生问题:我们将尝试在新建线程中的通道中发送完 `val` 值 **之后** 再使用它。尝试编译示例 16-9 中的代码并看看为何这是不允许的:
|
||||
所有权规则在消息传递中扮演了重要角色,其有助于我们编写安全的并发代码。防止并发编程中的错误是在 Rust 程序中考虑所有权的一大优势。现在让我们做一个试验来看看通道与所有权如何一同协作以避免产生问题:我们将尝试在新建线程中的通道中发送完 `val` 值 **之后** 再使用它。尝试编译示例 16-9 中的代码并看看为何这是不允许的:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -132,7 +131,7 @@ error[E0382]: use of moved value: `val`
|
||||
not implement the `Copy` trait
|
||||
```
|
||||
|
||||
我们的并发错误会造成一个编译时错误。`send` 函数获取其参数的所有权并移动这个值归接收者所有。这个意味着不可能意外的在发送后再次使用这个值;所有权系统检查一切是否合乎规则。
|
||||
我们的并发错误会造成一个编译时错误。`send` 函数获取其参数的所有权并移动这个值归接收者所有。这可以防止在发送后再次意外地使用这个值;所有权系统检查一切是否合乎规则。
|
||||
|
||||
### 发送多个值并观察接收者的等待
|
||||
|
||||
@ -187,7 +186,7 @@ Got: thread
|
||||
|
||||
### 通过克隆发送者来创建多个生产者
|
||||
|
||||
之前我们提到了`mpsc`是 _multiple producer, single consumer_ 的缩写。可以运用 `mpsc` 来扩展示例 16-11 中的代码来以创建都向同一接收者发送值的多个线程。这可以通过克隆通道的发送端在来做到,如示例 16-11 所示:
|
||||
之前我们提到了`mpsc`是 _multiple producer, single consumer_ 的缩写。可以运用 `mpsc` 来扩展示例 16-10 中的代码来创建向同一接收者发送值的多个线程。这可以通过克隆通道的发送端来做到,如示例 16-11 所示:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -255,6 +254,6 @@ Got: thread
|
||||
Got: you
|
||||
```
|
||||
|
||||
虽然你可能会看到这些值以不同的顺序出现;这依赖于你的系统。这也就是并发既有趣又困难的原因。如果通过 `thread::sleep` 做实验,在不同的线程中提供不同的值,就会发现他们的运行更加不确定并每次都会产生不同的输出。
|
||||
虽然你可能会看到这些值以不同的顺序出现;这依赖于你的系统。这也就是并发既有趣又困难的原因。如果通过 `thread::sleep` 做实验,在不同的线程中提供不同的值,就会发现他们的运行更加不确定,且每次都会产生不同的输出。
|
||||
|
||||
现在我们见识过了通道如何工作,再看看另一种不同的并发方式吧。
|
||||
|
@ -110,7 +110,7 @@ in previous iteration of loop
|
||||
which does not implement the `Copy` trait
|
||||
```
|
||||
|
||||
错误信息表明 `counter` 值在上一次循环中被移动了。所有 Rust 告诉我们不能将 `counter` 锁的所有权移动到多个线程中。让我们通过一个第十五章讨论过的多所有权手段来修复这个编译错误。
|
||||
错误信息表明 `counter` 值在上一次循环中被移动了。所以 Rust 告诉我们不能将 `counter` 锁的所有权移动到多个线程中。让我们通过一个第十五章讨论过的多所有权手段来修复这个编译错误。
|
||||
|
||||
#### 多线程和多所有权
|
||||
|
||||
@ -165,7 +165,7 @@ is not implemented for `std::rc::Rc<std::sync::Mutex<i32>>`
|
||||
= note: required by `std::thread::spawn`
|
||||
```
|
||||
|
||||
哇哦,错误信息太长不看!这里是一些需要注意的重要部分:第一行错误表明 `` `std::rc::Rc<std::sync::Mutex<i32>>` cannot be sent between threads safely ``。编译器也告诉了我们原因 `` the trait bound `Send` is not satisfied ``。下一部分会讲到 `Send`:这是确保所使用的类型意在用于并发环境的 trait 之一。
|
||||
哇哦,错误信息太长不看!这里是一些需要注意的重要部分:第一行错误表明 `` `std::rc::Rc<std::sync::Mutex<i32>>` cannot be sent between threads safely ``。编译器也告诉了我们原因 `` the trait bound `Send` is not satisfied ``。下一部分会讲到 `Send`:这是确保所使用的类型可以用于并发环境的 trait 之一。
|
||||
|
||||
不幸的是,`Rc<T>` 并不能安全的在线程间共享。当 `Rc<T>` 管理引用计数时,它必须在每一个 `clone` 调用时增加计数,并在每一个克隆被丢弃时减少计数。`Rc<T>` 并没有使用任何并发原语,来确保改变计数的操作不会被其他线程打断。在计数出错时可能会导致诡异的 bug,比如可能会造成内存泄漏,或在使用结束之前就丢弃一个值。我们所需要的是一个完全类似 `Rc<T>`,又以一种线程安全的方式改变引用计数的类型。
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user