@ -1,15 +1,18 @@
## 使用消息传递在线程间传送数据
> [ch16-02-message-passing.md ](https://github.com/rust-lang/book/blob/main/src/ch16-02-message-passing.md ) < br >
> commit 24e275d624fe85af7b5b6316e78f8bfbbcac23e7
> [ch16-02-message-passing.md ](https://github.com/rust-lang/book/blob/main/src/ch16-02-message-passing.md )
> < br >
> commit 36383b4da21dbd0a0781473bc8ad7ef0ed1b6751
一个日益流行的确保安全并发的方式是 ** 消息传递**( _message passing_) , 这里线程或 actor 通过发送包含数据的消息来相互沟通。这个思想来源于 [Go 编程语言文档中 ](https://golang.org/doc/effective_go.html#concurrency ) 的口号: “不要通过共享内存来通讯; 而是通过通讯来共享内存。”( “Do not communicate by sharing memory; instead, share memory by communicating.”)
Rust 中一个实现消息传递并发的主要工具是 ** 信道**( _channel_) , Rust 标准库提供了其实现的编程概念。你可以将其想象为一个水流的渠道,比如河流或小溪。如果你将诸如橡皮鸭或小船之类的东西放入其中,它们会顺流而下到达下游。
为了实现消息传递并发, Rust 标准库提供了一个 ** 信道**( _channel_) 实现。信道是一个通用编程概念, 表示数据从一个线程发送到另一个线程。
你可以将编程中的信道想象为一个水流的渠道,比如河流或小溪。如果你将诸如橡皮鸭或小船之类的东西放入其中,它们会顺流而下到达下游。
编程中的信息渠道( 信道) 有两部分组成, 一个发送者( transmitter) 和一个接收者( receiver) 。发送者位于上游位置, 在这里可以将橡皮鸭放入河中, 接收者则位于下游, 橡皮鸭最终会漂流至此。代码中的一部分调用发送者的方法以及希望发送的数据, 另一部分则检查接收端收到的消息。当发送者或接收者任一被丢弃时可以认为信道被 ** 关闭**( _closed_) 了。
这里,我们将开发一个程序,它会在一个线程生成值向信道发送,而在另一个线程会接收值并打印出来。这里会通过信道在线程间发送简单值来演示这个功能。一旦你熟悉了这项技术,就能使用信道来实现 聊天系统,或利用很多线程进行分布式计算并将部分计算结果发送给一个线程进行聚合。
这里,我们将开发一个程序,它会在一个线程生成值向信道发送,而在另一个线程会接收值并打印出来。这里会通过信道在线程间发送简单值来演示这个功能。一旦你熟悉了这项技术,你就可以将信道用于任何相互通信的任何线程,例如一个 聊天系统,或利用很多线程进行分布式计算并将部分计算结果发送给一个线程进行聚合。
首先,在示例 16-6 中,创建了一个信道但没有做任何事。注意这还不能编译,因为 Rust 不知道我们想要在信道中发送什么类型:
@ -23,7 +26,7 @@ Rust 中一个实现消息传递并发的主要工具是 **信道**( _channel_
这里使用 `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 所示。这类似于在河的上游扔下一只橡皮鸭或从一个线程向另一个线程发送聊天信息:
@ -35,11 +38,9 @@ Rust 中一个实现消息传递并发的主要工具是 **信道**( _channel_
< span class = "caption" > 示例 16-7: 将 `tx` 移动到一个新建的线程中并发送 “hi”</ span >
这里再次使用 `thread::spawn` 来创建一个新线程并使用 `move` 将 `tx` 移动到闭包中这样新建线程就拥有 `tx` 了。新建线程需要拥有信道的发送端以便能向信道发送消息。
信道的发送端有一个 `send` 方法用来获取需要放入信道的值。`send` 方法返回一个 `Result<T, E>` 类型,所以如果接收端已经被丢弃了,将没有发送值的目标,所以发送操作会返回错误。在这个例子中,出错的时候调用 `unwrap` 产生 panic。不过对于一个真实程序, 需要合理地处理它: 回到第九章复习正确处理错误的策略。
这里再次使用 `thread::spawn` 来创建一个新线程并使用 `move` 将 `tx` 移动到闭包中这样新建线程就拥有 `tx` 了。新建线程需要拥有信道的发送端以便能向信道发送消息。信道的发送端有一个 `send` 方法用来获取需要放入信道的值。`send` 方法返回一个 `Result<T, E>` 类型,所以如果接收端已经被丢弃了,将没有发送值的目标,所以发送操作会返回错误。在这个例子中,出错的时候调用 `unwrap` 产生 panic。不过对于一个真实程序, 需要合理地处理它: 回到第九章复习正确处理错误的策略。
在示例 16-8 中,我们在主线程中从信道的接收端 获取值。这类似于在河的下游捞起橡皮鸭或接收聊天信息:
在示例 16-8 中,我们在主线程中从信道的接收者获取值。这类似于在河的下游捞起橡皮鸭或接收聊天信息:
< span class = "filename" > 文件名: src/main.rs< / span >
@ -49,7 +50,7 @@ Rust 中一个实现消息传递并发的主要工具是 **信道**( _channel_
< 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` ,在有可用消息时进行处理,其余时候则处理一会其他工作直到再次检查。
@ -112,7 +113,7 @@ Got: thread
### 通过克隆发送者来创建多个生产者
之前我们提到了`mpsc`是 _multiple producer, single consumer_ 的缩写。可以运用 `mpsc` 来扩展示例 16-10 中的代码来创建向同一接收者发送值的多个线程。这可以通过克隆信道的发送端 来做到,如示例 16-11 所示:
之前我们提到了`mpsc`是 _multiple producer, single consumer_ 的缩写。可以运用 `mpsc` 来扩展示例 16-10 中的代码来创建向同一接收者发送值的多个线程。这可以通过克隆发送者 来做到,如示例 16-11 所示:
< span class = "filename" > 文件名: src/main.rs< / span >
@ -122,7 +123,7 @@ Got: thread
< span class = "caption" > 示例 16-11: 从多个生产者发送多个消息< / span >
这一次,在创建新线程之前,我们对信道的发送端 调用了 `clone` 方法。这会给我们一个可以传递给第一个新建线程的发送端句柄。我们会将原始的信道发送端传递给第二个新建线程。这样就会有两个线程,每个线程将向信道的接收端发送不同的消息。
这一次,在创建新线程之前,我们对发送者 调用了 `clone` 方法。这会给我们一个可以传递给第一个新建线程的发送端句柄。我们会将原始的信道发送端传递给第二个新建线程。这样就会有两个线程,每个线程将向信道的接收端发送不同的消息。
如果运行这些代码,你 ** 可能** 会看到这样的输出: