@ -3,14 +3,13 @@
> [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
> 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_) 了。
这里,我们将开发一个程序,它会在一个线程生成值向通道发送,而在另一个线程会接收值并打印出来。这里会通过通道在线程间发送简单值来演示这个功能。一旦你熟悉了这项技术,就能使用通道来实现聊天系统或利用很多线程进行分布式计算并将部分计算结果发送给一个线程进行聚合。
这里,我们将开发一个程序,它会在一个线程生成值向通道发送,而在另一个线程会接收值并打印出来。这里会通过通道在线程间发送简单值来演示这个功能。一旦你熟悉了这项技术,就能使用通道来实现聊天系统, 或利用很多线程进行分布式计算并将部分计算结果发送给一个线程进行聚合。
首先,在示例 16-6 中,创建了一个通道但没有做任何事。注意这还不能编译,因为 Rust 不知道我们想要在通道中发送什么类型:
首先,在示例 16-6 中,创建了一个通道但没有做任何事。注意这还不能编译,因为 Rust 不知道我们想要在通道中发送什么类型:
@ -30,7 +29,7 @@ fn main() {
`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 所示。这类似于 在河的上游扔下一只橡皮鸭或从一个线程向另一个线程发送聊天信息:
< span class = "filename" > 文件名: src/main.rs< / span >
< span class = "filename" > 文件名: src/main.rs< / span >
@ -79,7 +78,7 @@ fn main() {
通道的接收端有两个有用的方法:`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` , 在 有可用消息时进行处理,其余时候则处理一会其他工作直到再次检查。
出于简单的考虑,这个例子使用了 `recv` ;主线程中除了等待消息之外没有任何其他工作,所以阻塞主线程是合适的。
出于简单的考虑,这个例子使用了 `recv` ;主线程中除了等待消息之外没有任何其他工作,所以阻塞主线程是合适的。
@ -93,7 +92,7 @@ Got: hi
### 通道与所有权转移
### 通道与所有权转移
所有权规则在消息传递中扮演了重要角色,其有助于我们编写安全的并发代码。在并发编程中避免错误是在整个 Rust 程序中必须思考所有权所换来 的一大优势。现在让我们做一个试验来看看通道与所有权如何一同协作以避免产生问题:我们将尝试在新建线程中的通道中发送完 `val` 值 ** 之后** 再使用它。尝试编译示例 16-9 中的代码并看看为何这是不允许的:
所有权规则在消息传递中扮演了重要角色,其有助于我们编写安全的并发代码。防止并发编程中的错误是在 Rust 程序中考虑所有权 的一大优势。现在让我们做一个试验来看看通道与所有权如何一同协作以避免产生问题:我们将尝试在新建线程中的通道中发送完 `val` 值 ** 之后** 再使用它。尝试编译示例 16-9 中的代码并看看为何这是不允许的:
< span class = "filename" > 文件名: src/main.rs< / span >
< span class = "filename" > 文件名: src/main.rs< / span >
@ -132,7 +131,7 @@ error[E0382]: use of moved value: `val`
not implement the `Copy` trait
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 >
< span class = "filename" > 文件名: src/main.rs< / span >
@ -255,6 +254,6 @@ Got: thread
Got: you
Got: you
```
```
虽然你可能会看到这些值以不同的顺序出现;这依赖于你的系统。这也就是并发既有趣又困难的原因。如果通过 `thread::sleep` 做实验,在不同的线程中提供不同的值,就会发现他们的运行更加不确定并 每次都会产生不同的输出。
虽然你可能会看到这些值以不同的顺序出现;这依赖于你的系统。这也就是并发既有趣又困难的原因。如果通过 `thread::sleep` 做实验,在不同的线程中提供不同的值,就会发现他们的运行更加不确定,且 每次都会产生不同的输出。
现在我们见识过了通道如何工作,再看看另一种不同的并发方式吧。
现在我们见识过了通道如何工作,再看看另一种不同的并发方式吧。