2017-03-18 21:39:27 +08:00
<!DOCTYPE HTML>
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< title > 消息传递 - Rust 程序设计语言 简体中文版< / title >
< meta content = "text/html; charset=utf-8" http-equiv = "Content-Type" >
< meta name = "description" content = "Rust 程序设计语言 简体中文版" >
< meta name = "viewport" content = "width=device-width, initial-scale=1" >
< base href = "" >
< link rel = "stylesheet" href = "book.css" >
< link href = 'https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800' rel = 'stylesheet' type = 'text/css' >
< link rel = "shortcut icon" href = "favicon.png" >
<!-- Font Awesome -->
< link rel = "stylesheet" href = "https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" >
< link rel = "stylesheet" href = "highlight.css" >
< link rel = "stylesheet" href = "tomorrow-night.css" >
<!-- MathJax -->
< script type = "text/javascript" src = "https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML" > < / script >
<!-- Fetch JQuery from CDN but have a local fallback -->
< script src = "https://code.jquery.com/jquery-2.1.4.min.js" > < / script >
< script >
if (typeof jQuery == 'undefined') {
document.write(unescape("%3Cscript src='jquery.js'%3E%3C/script%3E"));
}
< / script >
< / head >
< body class = "light" >
<!-- Set the theme before any content is loaded, prevents flash -->
< script type = "text/javascript" >
var theme = localStorage.getItem('theme');
if (theme == null) { theme = 'light'; }
$('body').removeClass().addClass(theme);
< / script >
<!-- Hide / unhide sidebar before it is displayed -->
< script type = "text/javascript" >
var sidebar = localStorage.getItem('sidebar');
if (sidebar === "hidden") { $("html").addClass("sidebar-hidden") }
else if (sidebar === "visible") { $("html").addClass("sidebar-visible") }
< / script >
< div id = "sidebar" class = "sidebar" >
2017-04-18 14:55:09 +08:00
< ul class = "chapter" > < li > < a href = "ch01-00-introduction.html" > < strong > 1.< / strong > 介绍< / a > < / li > < li > < ul class = "section" > < li > < a href = "ch01-01-installation.html" > < strong > 1.1.< / strong > 安装< / a > < / li > < li > < a href = "ch01-02-hello-world.html" > < strong > 1.2.< / strong > Hello, World!< / a > < / li > < / ul > < / li > < li > < a href = "ch02-00-guessing-game-tutorial.html" > < strong > 2.< / strong > 猜猜看教程< / a > < / li > < li > < a href = "ch03-00-common-programming-concepts.html" > < strong > 3.< / strong > 通用编程概念< / a > < / li > < li > < ul class = "section" > < li > < a href = "ch03-01-variables-and-mutability.html" > < strong > 3.1.< / strong > 变量和可变性< / a > < / li > < li > < a href = "ch03-02-data-types.html" > < strong > 3.2.< / strong > 数据类型< / a > < / li > < li > < a href = "ch03-03-how-functions-work.html" > < strong > 3.3.< / strong > 函数如何工作< / a > < / li > < li > < a href = "ch03-04-comments.html" > < strong > 3.4.< / strong > 注释< / a > < / li > < li > < a href = "ch03-05-control-flow.html" > < strong > 3.5.< / strong > 控制流< / a > < / li > < / ul > < / li > < li > < a href = "ch04-00-understanding-ownership.html" > < strong > 4.< / strong > 认识所有权< / a > < / li > < li > < ul class = "section" > < li > < a href = "ch04-01-what-is-ownership.html" > < strong > 4.1.< / strong > 什么是所有权< / a > < / li > < li > < a href = "ch04-02-references-and-borrowing.html" > < strong > 4.2.< / strong > 引用 & 借用< / a > < / li > < li > < a href = "ch04-03-slices.html" > < strong > 4.3.< / strong > Slices< / a > < / li > < / ul > < / li > < li > < a href = "ch05-00-structs.html" > < strong > 5.< / strong > 结构体< / a > < / li > < li > < ul class = "section" > < li > < a href = "ch05-01-method-syntax.html" > < strong > 5.1.< / strong > 方法语法< / a > < / li > < / ul > < / li > < li > < a href = "ch06-00-enums.html" > < strong > 6.< / strong > 枚举和模式匹配< / a > < / li > < li > < ul class = "section" > < li > < a href = "ch06-01-defining-an-enum.html" > < strong > 6.1.< / strong > 定义枚举< / a > < / li > < li > < a href = "ch06-02-match.html" > < strong > 6.2.< / strong > < code > match< / code > 控制流运算符< / a > < / li > < li > < a href = "ch06-03-if-let.html" > < strong > 6.3.< / strong > < code > if let< / code > 简单控制流< / a > < / li > < / ul > < / li > < li > < a href = "ch07-00-modules.html" > < strong > 7.< / strong > 模块< / a > < / li > < li > < ul class = "section" > < li > < a href = "ch07-01-mod-and-the-filesystem.html" > < strong > 7.1.< / strong > < code > mod< / code > 和文件系统< / a > < / li > < li > < a href = "ch07-02-controlling-visibility-with-pub.html" > < strong > 7.2.< / strong > 使用< code > pub< / code > 控制可见性< / a > < / li > < li > < a href = "ch07-03-importing-names-with-use.html" > < strong > 7.3.< / strong > 使用< code > use< / code > 导入命名< / a > < / li > < / ul > < / li > < li > < a href = "ch08-00-common-collections.html" > < strong > 8.< / strong > 通用集合类型< / a > < / li > < li > < ul class = "section" > < li > < a href = "ch08-01-vectors.html" > < strong > 8.1.< / strong > vector< / a > < / li > < li > < a href = "ch08-02-strings.html" > < strong > 8.2.< / strong > 字符串< / a > < / li > < li > < a href = "ch08-03-hash-maps.html" > < strong > 8.3.< / strong > 哈希 map< / a > < / li > < / ul > < / li > < li > < a href = "ch09-00-error-handling.html" > < strong > 9.< / strong > 错误处理< / a > < / li > < li > < ul class = "section" > < li > < a href = "ch09-01-unrecoverable-errors-with-panic.html" > < strong > 9.1.< / strong > < code > panic!< / code > 与不可恢复的错误< / a > < / li > < li > < a href = "ch09-02-recoverable-errors-with-result.html" > < strong > 9.2.< / strong > < code > Result< / code > 与可恢复的错误< / a > < / li > < li > < a href = "ch09-03-to-panic-or-not-to-panic.html" > < strong > 9.3.< / strong > < code > panic!< / code > 还是不< code > panic!< / code > < / a > < / li > < / ul > < / li > < li > < a href = "ch10-00-generics.html" > < strong > 10.< / strong > 泛型、trait 和生命周期< / a > < / li > < li > < ul class = "section" > < li > < a href = "ch10-01-syntax.html" > < strong > 10.1.< / strong > 泛型数据类型< / a > < / li > < li > < a href = "ch10-02-traits.html" > < strong > 10.2.< / strong > trait: 定义共享的行为< / a > < / li > < li > < a href = "ch10-03-lifetime-syntax.html" > < strong > 10.3.< / strong > 生命周期与引用有效性< / a > < / li > < / ul > < / li > < li > < a href = "ch11-00-testing.html" > < strong > 11.< / strong > 测试< / a > < / li > < li > < ul class = "section" > < li > < a href = "ch11-01-writing-tests.html" > < strong > 11.1.< / strong > 编写测试< / a > < / li > < li > < a href = "ch11-02-running-tests.html" > < strong > 11.2.< / strong > 运行测试< / a > < / li > < li > < a href = "ch11-03-test-organization.html" > < strong > 11.3.< / strong > 测试的组织结构< / a > < / li > < / ul > < / li > < li > < a href = "ch12-00-an-io-project.h
2017-03-18 21:39:27 +08:00
< / div >
< div id = "page-wrapper" class = "page-wrapper" >
< div class = "page" >
< div id = "menu-bar" class = "menu-bar" >
< div class = "left-buttons" >
< i id = "sidebar-toggle" class = "fa fa-bars" > < / i >
< i id = "theme-toggle" class = "fa fa-paint-brush" > < / i >
< / div >
< h1 class = "menu-title" > Rust 程序设计语言 简体中文版< / h1 >
< div class = "right-buttons" >
< i id = "print-button" class = "fa fa-print" title = "Print this book" > < / i >
< / div >
< / div >
< div id = "content" class = "content" >
2017-03-20 23:47:47 +08:00
< a class = "header" href = "#使用消息传递在线程间传送数据" name = "使用消息传递在线程间传送数据" > < h2 > 使用消息传递在线程间传送数据< / h2 > < / a >
< blockquote >
< p > < a href = "https://github.com/rust-lang/book/blob/master/second-edition/src/ch16-02-message-passing.md" > ch16-02-message-passing.md< / a >
< br >
commit da15de39eaabd50100d6fa662c653169254d9175< / p >
< / blockquote >
< p > 最近人气正在上升的一个并发方式是< strong > 消息传递< / strong > ( < em > message passing< / em > ),这里线程或 actor 通过发送包含数据的消息来沟通。这个思想来源于口号:< / p >
< blockquote >
< p > Do not communicate by sharing memory; instead, share memory by
communicating.< / p >
< p > 不要共享内存来通讯;而是要通讯来共享内存。< / p >
< p > --< a href = "http://golang.org/doc/effective_go.html" > Effective Go< / a > < / p >
< / blockquote >
< p > 实现这个目标的主要工具是< strong > 通道< / strong > ( < em > channel< / em > ) 。通道有两部分组成, 一个发送者( transmitter) 和一个接收者( receiver) 。代码的一部分可以调用发送者和想要发送的数据, 而另一部分代码可以在接收的那一端收取消息。< / p >
< p > 我们将编写一个例子使用一个线程生成值并向通道发送他们。主线程会接收这些值并打印出来。< / p >
< p > 首先,如列表 16-6 所示,先创建一个通道但不做任何事:< / p >
< p > < span class = "filename" > Filename: src/main.rs< / span > < / p >
< pre > < code class = "language-rust" > use std::sync::mpsc;
fn main() {
let (tx, rx) = mpsc::channel();
# tx.send(()).unwrap();
}
< / code > < / pre >
< p > < span class = "caption" > Listing 16-6: Creating a channel and assigning the two
halves to < code > tx< / code > and < code > rx< / code > < / span > < / p >
< p > < code > mpsc::channel< / code > 函数创建一个新的通道。< code > mpsc< / code > 是< strong > 多个生产者,单个消费者< / strong > ( < em > multiple producer, single consumer< / em > )的缩写。简而言之,可以有多个产生值的< strong > 发送端< / strong > ,但只能有一个消费这些值的< strong > 接收端< / strong > 。现在我们以一个单独的生产者开始,不过一旦例子可以工作了就会增加多个生产者。< / p >
< p > < code > mpsc::channel< / code > 返回一个元组:第一个元素是发送端,而第二个元素是接收端。由于历史原因,很多人使用< code > tx< / code > 和< code > rx< / code > 作为< strong > 发送者< / strong > 和< strong > 接收者< / strong > 的缩写,所以这就是我们将用来绑定这两端变量的名字。这里使用了一个< code > let< / code > 语句和模式来解构了元组。第十八章会讨论< code > let< / code > 语句中的模式和解构。< / p >
< p > 让我们将发送端移动到一个新建线程中并发送一个字符串,如列表 16-7 所示:< / p >
< p > < span class = "filename" > Filename: src/main.rs< / span > < / p >
< pre > < code class = "language-rust" > use std::thread;
use std::sync::mpsc;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let val = String::from(" hi" );
tx.send(val).unwrap();
});
}
< / code > < / pre >
< p > < span class = "caption" > Listing 16-7: Moving < code > tx< / code > to a spawned thread and sending
" hi" < / span > < / p >
< p > 正如上一部分那样使用< code > thread::spawn< / code > 来创建一个新线程。并使用一个< code > move< / code > 闭包来将< code > tx< / code > 移动进闭包这样新建线程就是其所有者。< / p >
< p > 通道的发送端有一个< code > send< / code > 方法用来获取需要放入通道的值。< code > send< / code > 方法返回一个< code > Result< T, E> < / code > 类型,因为如果接收端被丢弃了,将没有发送值的目标,所以发送操作会出错。在这个例子中,我们简单的调用< code > unwrap< / code > 来忽略错误,不过对于一个真实程序,需要合理的处理它。第九章是你复习正确错误处理策略的好地方。< / p >
< p > 在列表 16-8 中,让我们在主线程中从通道的接收端获取值:< / p >
< p > < span class = "filename" > Filename: src/main.rs< / span > < / p >
< pre > < code class = "language-rust" > use std::thread;
use std::sync::mpsc;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let val = String::from(" hi" );
tx.send(val).unwrap();
});
let received = rx.recv().unwrap();
println!(" Got: {}" , received);
}
< / code > < / pre >
< p > < span class = "caption" > Listing 16-8: Receiving the value " hi" in the main thread
and printing it out< / span > < / p >
< p > 通道的接收端有两个有用的方法:< code > recv< / code > 和< code > try_recv< / code > 。这里,我们使用了< code > recv< / code > ,它是 < em > receive< / em > 的缩写。这个方法会阻塞执行直到从通道中接收一个值。一旦发送了一个值,< code > recv< / code > 会在一个< code > Result< T, E> < / code > 中返回它。当通道发送端关闭,< code > recv< / code > 会返回一个错误。< code > try_recv< / code > 不会阻塞;相反它立刻返回一个< code > Result< T, E> < / code > 。< / p >
< p > 如果运行列表 16-8 中的代码,我们将会看到主线程打印出这个值:< / p >
< pre > < code > Got: hi
< / code > < / pre >
< a class = "header" href = "#通道与所有权如何交互" name = "通道与所有权如何交互" > < h3 > 通道与所有权如何交互< / h3 > < / a >
< p > 现在让我们做一个试验来看看通道与所有权如何在一起工作:我们将尝试在新建线程中的通道中发送完< code > val< / code > 之后再使用它。尝试编译列表 16-9 中的代码:< / p >
< p > < span class = "filename" > Filename: src/main.rs< / span > < / p >
< pre > < code class = "language-rust,ignore" > use std::thread;
use std::sync::mpsc;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let val = String::from(" hi" );
tx.send(val).unwrap();
println!(" val is {}" , val);
});
let received = rx.recv().unwrap();
println!(" Got: {}" , received);
}
< / code > < / pre >
< p > < span class = "caption" > Listing 16-9: Attempting to use < code > val< / code > after we have sent
it down the channel< / span > < / p >
< p > 这里尝试在通过< code > tx.send< / code > 发送< code > val< / code > 到通道中之后将其打印出来。这是一个坏主意:一旦将值发送到另一个线程后,那个线程可能会在我们在此使用它之前就修改或者丢弃它。这会由于不一致或不存在的数据而导致错误或意外的结果。< / p >
< p > 尝试编译这些代码, Rust 会报错:< / p >
< pre > < code > error[E0382]: use of moved value: `val`
--> src/main.rs:10:31
|
9 | tx.send(val).unwrap();
| --- value moved here
10 | println!(" val is {}" , val);
| ^^^ value used here after move
|
= note: move occurs because `val` has type `std::string::String`, which does
not implement the `Copy` trait
< / code > < / pre >
< p > 我们的并发错误会造成一个编译时错误!< code > send< / code > 获取其参数的所有权并移动这个值归接收者所有。这个意味着不可能意外的在发送后再次使用这个值;所有权系统检查一切是否合乎规则。< / p >
< p > 在这一点上,消息传递非常类似于 Rust 的单所有权系统。消息传递的拥护者出于相似的原因支持消息传递,就像 Rustacean 们欣赏 Rust 的所有权一样:单所有权意味着特定类型问题的消失。如果一次只有一个线程可以使用某些内存,就没有出现数据竞争的机会。< / p >
< a class = "header" href = "#发送多个值并观察接收者的等待" name = "发送多个值并观察接收者的等待" > < h3 > 发送多个值并观察接收者的等待< / h3 > < / a >
< p > 列表 16-8 中的代码可以编译和运行,不过这并不是很有趣:通过它难以看出两个独立的线程在一个通道上相互通讯。列表 16-10 则有一些改进会证明这些代码是并发执行的:新建线程现在会发送多个消息并在每个消息之间暂定一段时间。< / p >
< p > < span class = "filename" > Filename: src/main.rs< / span > < / p >
< pre > < code class = "language-rust" > use std::thread;
use std::sync::mpsc;
use std::time::Duration;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let vals = vec![
String::from(" hi" ),
String::from(" from" ),
String::from(" the" ),
String::from(" thread" ),
];
for val in vals {
tx.send(val).unwrap();
thread::sleep(Duration::new(1, 0));
}
});
for received in rx {
println!(" Got: {}" , received);
}
}
< / code > < / pre >
< p > < span class = "caption" > Listing 16-10: Sending multiple messages and pausing
between each one< / span > < / p >
< p > 这一次,在新建线程中有一个字符串 vector 希望发送到主线程。我们遍历他们,单独的发送每一个字符串并通过一个< code > Duration< / code > 值调用< code > thread::sleep< / code > 函数来暂停一秒。< / p >
< p > 在主线程中,不再显式的调用< code > recv< / code > 函数:而是将< code > rx< / code > 当作一个迭代器。对于每一个接收到的值,我们将其打印出来。当通道被关闭时,迭代器也将结束。< / p >
< p > 当运行列表 16-10 中的代码时,将看到如下输出,每一行都会暂停一秒:< / p >
< pre > < code > Got: hi
Got: from
Got: the
Got: thread
< / code > < / pre >
< p > 在主线程中并没有任何暂停或位于< code > for< / code > 循环中用于等待的代码,所以可以说主线程是在等待从新建线程中接收值。< / p >
< a class = "header" href = "#通过克隆发送者来创建多个生产者" name = "通过克隆发送者来创建多个生产者" > < h3 > 通过克隆发送者来创建多个生产者< / h3 > < / a >
< p > 差不多在本部分的开头,我们提到了< code > mpsc< / code > 是 < em > multiple producer, single consumer< / em > 的缩写。可以扩展列表 16-11 中的代码来创建都向同一接收者发送值的多个线程。这可以通过克隆通道的发送端在来做到,如列表 16-11 所示:< / p >
< p > < span class = "filename" > Filename: src/main.rs< / span > < / p >
< pre > < code class = "language-rust" > # use std::thread;
# use std::sync::mpsc;
# use std::time::Duration;
#
# fn main() {
// ...snip...
let (tx, rx) = mpsc::channel();
let tx1 = tx.clone();
thread::spawn(move || {
let vals = vec![
String::from(" hi" ),
String::from(" from" ),
String::from(" the" ),
String::from(" thread" ),
];
for val in vals {
tx1.send(val).unwrap();
thread::sleep(Duration::new(1, 0));
}
});
thread::spawn(move || {
let vals = vec![
String::from(" more" ),
String::from(" messages" ),
String::from(" for" ),
String::from(" you" ),
];
for val in vals {
tx.send(val).unwrap();
thread::sleep(Duration::new(1, 0));
}
});
// ...snip...
#
# for received in rx {
# println!(" Got: {}" , received);
# }
# }
< / code > < / pre >
< p > < span class = "caption" > Listing 16-11: Sending multiple messages and pausing
between each one< / span > < / p >
< p > 这一次,在创建新线程之前,我们对通道的发送端调用了< code > clone< / code > 方法。这会给我们一个可以传递给第一个新建线程的发送端句柄。我们会将原始的通道发送端传递给第二个新建线程,这样每个线程将向通道的接收端发送不同的消息。< / p >
< p > 如果运行这些代码,你< strong > 可能< / strong > 会看到这样的输出:< / p >
< pre > < code > Got: hi
Got: more
Got: from
Got: messages
Got: for
Got: the
Got: thread
Got: you
< / code > < / pre >
< p > 虽然你可能会看到这些以不同的顺序出现。这依赖于你的系统!这也就是并发既有趣又困难的原因。如果你拿< code > thread::sleep< / code > 做实验,在不同的线程中提供不同的值,就会发现他们的运行更加不确定并每次都会产生不同的输出。< / p >
< p > 现在我们见识过了通道如何工作,再看看共享内存并发吧。< / p >
2017-03-18 21:39:27 +08:00
< / div >
<!-- Mobile navigation buttons -->
< a href = "ch16-01-threads.html" class = "mobile-nav-chapters previous" >
< i class = "fa fa-angle-left" > < / i >
< / a >
< a href = "ch16-03-shared-state.html" class = "mobile-nav-chapters next" >
< i class = "fa fa-angle-right" > < / i >
< / a >
< / div >
< a href = "ch16-01-threads.html" class = "nav-chapters previous" title = "You can navigate through the chapters using the arrow keys" >
< i class = "fa fa-angle-left" > < / i >
< / a >
< a href = "ch16-03-shared-state.html" class = "nav-chapters next" title = "You can navigate through the chapters using the arrow keys" >
< i class = "fa fa-angle-right" > < / i >
< / a >
< / div >
<!-- Local fallback for Font Awesome -->
< script >
if ($(".fa").css("font-family") !== "FontAwesome") {
$('< link rel = "stylesheet" type = "text/css" href = "_FontAwesome/css/font-awesome.css" > ').prependTo('head');
}
< / script >
<!-- Livereload script (if served using the cli tool) -->
< script src = "highlight.js" > < / script >
< script src = "book.js" > < / script >
< / body >
< / html >