@ -17,9 +17,9 @@
我们将编写一个例子使用一个线程生成值并向通道发送他们。主线程会接收这些值并打印出来。
我们将编写一个例子使用一个线程生成值并向通道发送他们。主线程会接收这些值并打印出来。
首先,如列表 16-6 所示,先创建一个通道但不做任何事:
首先,如示例 16-6 所示,先创建一个通道但不做任何事:
< span class = "filename" > Filename : src/main.rs< / span >
< span class = "filename" > 文件名 : src/main.rs< / span >
```rust
```rust
use std::sync::mpsc;
use std::sync::mpsc;
@ -30,16 +30,15 @@ fn main() {
}
}
```
```
< span class = "caption" > Listing 16-6: Creating a channel and assigning the two
< span class = "caption" > 示例 16-6: 创建一个通道,并指派一个包含 `tx` 和 `rx` 的元组</ span >
halves to `tx` and `rx` </ span >
`mpsc::channel` 函数创建一个新的通道。`mpsc`是**多个生产者,单个消费者**( *multiple producer, single consumer*)的缩写。简而言之,可以有多个产生值的**发送端**,但只能有一个消费这些值的**接收端**。现在我们以一个单独的生产者开始,不过一旦例子可以工作了就会增加多个生产者。
`mpsc::channel` 函数创建一个新的通道。`mpsc`是**多个生产者,单个消费者**( *multiple producer, single consumer*)的缩写。简而言之,可以有多个产生值的**发送端**,但只能有一个消费这些值的**接收端**。现在我们以一个单独的生产者开始,不过一旦例子可以工作了就会增加多个生产者。
`mpsc::channel` 返回一个元组:第一个元素是发送端,而第二个元素是接收端。由于历史原因,很多人使用`tx`和`rx`作为**发送者**和**接收者**的缩写,所以这就是我们将用来绑定这两端变量的名字。这里使用了一个`let`语句和模式来解构了元组。第十八章会讨论`let`语句中的模式和解构。
`mpsc::channel` 返回一个元组:第一个元素是发送端,而第二个元素是接收端。由于历史原因,很多人使用`tx`和`rx`作为**发送者**和**接收者**的缩写,所以这就是我们将用来绑定这两端变量的名字。这里使用了一个`let`语句和模式来解构了元组。第十八章会讨论`let`语句中的模式和解构。
让我们将发送端移动到一个新建线程中并发送一个字符串,如列表 16-7 所示:
让我们将发送端移动到一个新建线程中并发送一个字符串,如示例 16-7 所示:
< span class = "filename" > Filename : src/main.rs< / span >
< span class = "filename" > 文件名 : src/main.rs< / span >
```rust
```rust
use std::thread;
use std::thread;
@ -55,16 +54,15 @@ fn main() {
}
}
```
```
< span class = "caption" > Listing 16-7: Moving `tx` to a spawned thread and sending
< span class = "caption" > 示例 16-7: 将 `tx` 移动到一个新建的线程中并发送内容 "hi"</ span >
"hi"< / span >
正如上一部分那样使用`thread::spawn`来创建一个新线程。并使用一个`move`闭包来将`tx`移动进闭包这样新建线程就是其所有者。
正如上一部分那样使用`thread::spawn`来创建一个新线程。并使用一个`move`闭包来将`tx`移动进闭包这样新建线程就是其所有者。
通道的发送端有一个`send`方法用来获取需要放入通道的值。`send`方法返回一个`Result< T , E > `类型,因为如果接收端被丢弃了,将没有发送值的目标,所以发送操作会出错。在这个例子中,我们简单的调用`unwrap`来忽略错误,不过对于一个真实程序,需要合理的处理它。第九章是你复习正确错误处理策略的好地方。
通道的发送端有一个`send`方法用来获取需要放入通道的值。`send`方法返回一个`Result< T , E > `类型,因为如果接收端被丢弃了,将没有发送值的目标,所以发送操作会出错。在这个例子中,我们简单的调用`unwrap`来忽略错误,不过对于一个真实程序,需要合理的处理它。第九章是你复习正确错误处理策略的好地方。
在列表 16-8 中,让我们在主线程中从通道的接收端获取值:
在示例 16-8 中,让我们在主线程中从通道的接收端获取值:
< span class = "filename" > Filename : src/main.rs< / span >
< span class = "filename" > 文件名 : src/main.rs< / span >
```rust
```rust
use std::thread;
use std::thread;
@ -83,12 +81,11 @@ fn main() {
}
}
```
```
< span class = "caption" > Listing 16-8: Receiving the value "hi" in the main thread
< span class = "caption" > 示例 16-8: 在主线程中接收并打印内容 "hi"< / span >
and printing it out< / span >
通道的接收端有两个有用的方法:`recv`和`try_recv`。这里,我们使用了`recv`,它是 *receive* 的缩写。这个方法会阻塞执行直到从通道中接收一个值。一旦发送了一个值,`recv`会在一个`Result< T , E > `中返回它。当通道发送端关闭,`recv`会返回一个错误。`try_recv`不会阻塞;相反它立刻返回一个`Result< T , E > `。
通道的接收端有两个有用的方法:`recv`和`try_recv`。这里,我们使用了`recv`,它是 *receive* 的缩写。这个方法会阻塞执行直到从通道中接收一个值。一旦发送了一个值,`recv`会在一个`Result< T , E > `中返回它。当通道发送端关闭,`recv`会返回一个错误。`try_recv`不会阻塞;相反它立刻返回一个`Result< T , E > `。
如果运行列表 16-8 中的代码,我们将会看到主线程打印出这个值:
如果运行示例 16-8 中的代码,我们将会看到主线程打印出这个值:
```
```
Got: hi
Got: hi
@ -96,9 +93,9 @@ Got: hi
### 通道与所有权如何交互
### 通道与所有权如何交互
现在让我们做一个试验来看看通道与所有权如何在一起工作:我们将尝试在新建线程中的通道中发送完`val`之后再使用它。尝试编译列表 16-9 中的代码:
现在让我们做一个试验来看看通道与所有权如何在一起工作:我们将尝试在新建线程中的通道中发送完`val`之后再使用它。尝试编译示例 16-9 中的代码:
< span class = "filename" > Filename : src/main.rs< / span >
< span class = "filename" > 文件名 : src/main.rs< / span >
```rust,ignore
```rust,ignore
use std::thread;
use std::thread;
@ -118,8 +115,7 @@ fn main() {
}
}
```
```
< span class = "caption" > Listing 16-9: Attempting to use `val` after we have sent
< span class = "caption" > 示例 16-9: 在我们已经发送到通道中后,尝试使用 `val` 引用</ span >
it down the channel< / span >
这里尝试在通过`tx.send`发送`val`到通道中之后将其打印出来。这是一个坏主意:一旦将值发送到另一个线程后,那个线程可能会在我们在此使用它之前就修改或者丢弃它。这会由于不一致或不存在的数据而导致错误或意外的结果。
这里尝试在通过`tx.send`发送`val`到通道中之后将其打印出来。这是一个坏主意:一旦将值发送到另一个线程后,那个线程可能会在我们在此使用它之前就修改或者丢弃它。这会由于不一致或不存在的数据而导致错误或意外的结果。
@ -144,9 +140,9 @@ error[E0382]: use of moved value: `val`
### 发送多个值并观察接收者的等待
### 发送多个值并观察接收者的等待
列表 16-8 中的代码可以编译和运行,不过这并不是很有趣:通过它难以看出两个独立的线程在一个通道上相互通讯。列表 16-10 则有一些改进会证明这些代码是并发执行的:新建线程现在会发送多个消息并在每个消息之间暂停一段时间。
示例 16-8 中的代码可以编译和运行,不过这并不是很有趣:通过它难以看出两个独立的线程在一个通道上相互通讯。示例 16-10 则有一些改进会证明这些代码是并发执行的:新建线程现在会发送多个消息并在每个消息之间暂停一段时间。
< span class = "filename" > Filename : src/main.rs< / span >
< span class = "filename" > 文件名 : src/main.rs< / span >
```rust
```rust
use std::thread;
use std::thread;
@ -176,14 +172,13 @@ fn main() {
}
}
```
```
< span class = "caption" > Listing 16-10: Sending multiple messages and pausing
< span class = "caption" > 示例 16-10: 发送多个消息,并在每次发送后暂停一段时间< / span >
between each one< / span >
这一次,在新建线程中有一个字符串 vector 希望发送到主线程。我们遍历他们,单独的发送每一个字符串并通过一个`Duration`值调用`thread::sleep`函数来暂停一秒。
这一次,在新建线程中有一个字符串 vector 希望发送到主线程。我们遍历他们,单独的发送每一个字符串并通过一个`Duration`值调用`thread::sleep`函数来暂停一秒。
在主线程中,不再显式的调用`recv`函数:而是将`rx`当作一个迭代器。对于每一个接收到的值,我们将其打印出来。当通道被关闭时,迭代器也将结束。
在主线程中,不再显式的调用`recv`函数:而是将`rx`当作一个迭代器。对于每一个接收到的值,我们将其打印出来。当通道被关闭时,迭代器也将结束。
当运行列表 16-10 中的代码时,将看到如下输出,每一行都会暂停一秒:
当运行示例 16-10 中的代码时,将看到如下输出,每一行都会暂停一秒:
```
```
Got: hi
Got: hi
@ -196,9 +191,9 @@ Got: thread
### 通过克隆发送者来创建多个生产者
### 通过克隆发送者来创建多个生产者
差不多在本部分的开头,我们提到了`mpsc`是 *multiple producer, single consumer* 的缩写。可以扩展列表 16-11 中的代码来创建都向同一接收者发送值的多个线程。这可以通过克隆通道的发送端在来做到,如列表 16-11 所示:
差不多在本部分的开头,我们提到了`mpsc`是 *multiple producer, single consumer* 的缩写。可以扩展示例 16-11 中的代码来创建都向同一接收者发送值的多个线程。这可以通过克隆通道的发送端在来做到,如示例 16-11 所示:
< span class = "filename" > Filename : src/main.rs< / span >
< span class = "filename" > 文件名 : src/main.rs< / span >
```rust
```rust
# use std::thread;
# use std::thread;
@ -245,8 +240,7 @@ thread::spawn(move || {
# }
# }
```
```
< span class = "caption" > Listing 16-11: Sending multiple messages and pausing
< span class = "caption" > 示例 16-11: 发送多个消息,并在每次发送后暂停一段时间< / span >
between each one< / span >
这一次,在创建新线程之前,我们对通道的发送端调用了`clone`方法。这会给我们一个可以传递给第一个新建线程的发送端句柄。我们会将原始的通道发送端传递给第二个新建线程,这样每个线程将向通道的接收端发送不同的消息。
这一次,在创建新线程之前,我们对通道的发送端调用了`clone`方法。这会给我们一个可以传递给第一个新建线程的发送端句柄。我们会将原始的通道发送端传递给第二个新建线程,这样每个线程将向通道的接收端发送不同的消息。