You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
trpl-zh-cn/ch16-02-message-passing.html

436 lines
43 KiB

<!DOCTYPE HTML>
<html lang="en" class="light" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>使用消息传递在线程间通信 - Rust 程序设计语言 简体中文版</title>
<!-- Custom HTML head -->
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="icon" href="favicon.svg">
<link rel="shortcut icon" href="favicon.png">
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/general.css">
<link rel="stylesheet" href="css/chrome.css">
<link rel="stylesheet" href="css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="highlight.css">
<link rel="stylesheet" href="tomorrow-night.css">
<link rel="stylesheet" href="ayu-highlight.css">
<!-- Custom theme stylesheets -->
<link rel="stylesheet" href="ferris.css">
<link rel="stylesheet" href="theme/2018-edition.css">
<link rel="stylesheet" href="theme/semantic-notes.css">
<link rel="stylesheet" href="theme/listing.css">
</head>
<body class="sidebar-visible no-js">
<div id="body-container">
<!-- Provide site root to javascript -->
<script>
var path_to_root = "";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('light')
html.classList.add(theme);
var body = document.querySelector('body');
body.classList.remove('no-js')
body.classList.add('js');
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
var body = document.querySelector('body');
var sidebar = null;
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
body.classList.remove('sidebar-visible');
body.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
<ol class="chapter"><li class="chapter-item expanded affix "><a href="title-page.html">Rust 程序设计语言</a></li><li class="chapter-item expanded affix "><a href="foreword.html">前言</a></li><li class="chapter-item expanded affix "><a href="ch00-00-introduction.html">简介</a></li><li class="chapter-item expanded "><a href="ch01-00-getting-started.html"><strong aria-hidden="true">1.</strong> 入门指南</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch01-01-installation.html"><strong aria-hidden="true">1.1.</strong> 安装</a></li><li class="chapter-item expanded "><a href="ch01-02-hello-world.html"><strong aria-hidden="true">1.2.</strong> Hello, World!</a></li><li class="chapter-item expanded "><a href="ch01-03-hello-cargo.html"><strong aria-hidden="true">1.3.</strong> Hello, Cargo!</a></li></ol></li><li class="chapter-item expanded "><a href="ch02-00-guessing-game-tutorial.html"><strong aria-hidden="true">2.</strong> 写个猜数字游戏</a></li><li class="chapter-item expanded "><a href="ch03-00-common-programming-concepts.html"><strong aria-hidden="true">3.</strong> 常见编程概念</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch03-01-variables-and-mutability.html"><strong aria-hidden="true">3.1.</strong> 变量与可变性</a></li><li class="chapter-item expanded "><a href="ch03-02-data-types.html"><strong aria-hidden="true">3.2.</strong> 数据类型</a></li><li class="chapter-item expanded "><a href="ch03-03-how-functions-work.html"><strong aria-hidden="true">3.3.</strong> 函数</a></li><li class="chapter-item expanded "><a href="ch03-04-comments.html"><strong aria-hidden="true">3.4.</strong> 注释</a></li><li class="chapter-item expanded "><a href="ch03-05-control-flow.html"><strong aria-hidden="true">3.5.</strong> 控制流</a></li></ol></li><li class="chapter-item expanded "><a href="ch04-00-understanding-ownership.html"><strong aria-hidden="true">4.</strong> 认识所有权</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch04-01-what-is-ownership.html"><strong aria-hidden="true">4.1.</strong> 什么是所有权?</a></li><li class="chapter-item expanded "><a href="ch04-02-references-and-borrowing.html"><strong aria-hidden="true">4.2.</strong> 引用与借用</a></li><li class="chapter-item expanded "><a href="ch04-03-slices.html"><strong aria-hidden="true">4.3.</strong> Slice 类型</a></li></ol></li><li class="chapter-item expanded "><a href="ch05-00-structs.html"><strong aria-hidden="true">5.</strong> 使用结构体组织相关联的数据</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch05-01-defining-structs.html"><strong aria-hidden="true">5.1.</strong> 结构体的定义和实例化</a></li><li class="chapter-item expanded "><a href="ch05-02-example-structs.html"><strong aria-hidden="true">5.2.</strong> 结构体示例程序</a></li><li class="chapter-item expanded "><a href="ch05-03-method-syntax.html"><strong aria-hidden="true">5.3.</strong> 方法语法</a></li></ol></li><li class="chapter-item expanded "><a href="ch06-00-enums.html"><strong aria-hidden="true">6.</strong> 枚举和模式匹配</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch06-01-defining-an-enum.html"><strong aria-hidden="true">6.1.</strong> 枚举的定义</a></li><li class="chapter-item expanded "><a href="ch06-02-match.html"><strong aria-hidden="true">6.2.</strong> match 控制流结构</a></li><li class="chapter-item expanded "><a href="ch06-03-if-let.html"><strong aria-hidden="true">6.3.</strong> if let 简洁控制流</a></li></ol></li><li class="chapter-item expanded "><a href="ch07-00-managing-growing-projects-with-packages-crates-and-modules.html"><strong aria-hidden="true">7.</strong> 使用包、Crate 和模块管理不断增长的项目</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch07-01-packages-and-crates.html"><strong aria-hidden="true">7.1.</strong> 包和 Crate</a></li><li class="chapter-item expanded "><a h
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<!-- Track and set sidebar scroll position -->
<script>
var sidebarScrollbox = document.querySelector('#sidebar .sidebar-scrollbox');
sidebarScrollbox.addEventListener('click', function(e) {
if (e.target.tagName === 'A') {
sessionStorage.setItem('sidebar-scroll', sidebarScrollbox.scrollTop);
}
}, { passive: true });
var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll');
sessionStorage.removeItem('sidebar-scroll');
if (sidebarScrollTop) {
// preserve sidebar scroll position when navigating via links within sidebar
sidebarScrollbox.scrollTop = sidebarScrollTop;
} else {
// scroll sidebar to current active section when navigating via "next/previous chapter" buttons
var activeSection = document.querySelector('#sidebar .active');
if (activeSection) {
activeSection.scrollIntoView({ block: 'center' });
}
}
</script>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">Rust 程序设计语言 简体中文版</h1>
<div class="right-buttons">
<a href="print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
<a href="https://github.com/KaiserY/trpl-zh-cn/tree/main" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa fa-github"></i>
</a>
<a href="https://github.com/KaiserY/trpl-zh-cn/edit/main/src/ch16-02-message-passing.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h2 id="使用消息传递在线程间传送数据"><a class="header" href="#使用消息传递在线程间传送数据">使用消息传递在线程间传送数据</a></h2>
<blockquote>
<p><a href="https://github.com/rust-lang/book/blob/main/src/ch16-02-message-passing.md">ch16-02-message-passing.md</a>
<br>
commit 36383b4da21dbd0a0781473bc8ad7ef0ed1b6751</p>
</blockquote>
<p>一个日益流行的确保安全并发的方式是 <strong>消息传递</strong><em>message passing</em>),这里线程或 actor 通过发送包含数据的消息来相互沟通。这个思想来源于 <a href="https://golang.org/doc/effective_go.html#concurrency">Go 编程语言文档中</a> 的口号“不要通过共享内存来通讯而是通过通讯来共享内存。”“Do not communicate by sharing memory; instead, share memory by communicating.”)</p>
<p>为了实现消息传递并发Rust 标准库提供了一个 <strong>信道</strong><em>channel</em>)实现。信道是一个通用编程概念,表示数据从一个线程发送到另一个线程。</p>
<p>你可以将编程中的信道想象为一个水流的渠道,比如河流或小溪。如果你将诸如橡皮鸭或小船之类的东西放入其中,它们会顺流而下到达下游。</p>
<p>编程中的信息渠道信道有两部分组成一个发送者transmitter和一个接收者receiver。发送者位于上游位置在这里可以将橡皮鸭放入河中接收者则位于下游橡皮鸭最终会漂流至此。代码中的一部分调用发送者的方法以及希望发送的数据另一部分则检查接收端收到的消息。当发送者或接收者任一被丢弃时可以认为信道被 <strong>关闭</strong><em>closed</em>)了。</p>
<p>这里,我们将开发一个程序,它会在一个线程生成值向信道发送,而在另一个线程会接收值并打印出来。这里会通过信道在线程间发送简单值来演示这个功能。一旦你熟悉了这项技术,你就可以将信道用于任何相互通信的任何线程,例如一个聊天系统,或利用很多线程进行分布式计算并将部分计算结果发送给一个线程进行聚合。</p>
<p>首先,在示例 16-6 中,创建了一个信道但没有做任何事。注意这还不能编译,因为 Rust 不知道我们想要在信道中发送什么类型:</p>
<p><span class="filename">文件名src/main.rs</span></p>
<pre><code class="language-rust ignore does_not_compile">use std::sync::mpsc;
fn main() {
let (tx, rx) = mpsc::channel();
}</code></pre>
<p><span class="caption">示例 16-6: 创建一个信道,并将其两端赋值给 <code>tx</code><code>rx</code></span></p>
<p>这里使用 <code>mpsc::channel</code> 函数创建一个新的信道;<code>mpsc</code><strong>多个生产者,单个消费者</strong><em>multiple producer, single consumer</em>的缩写。简而言之Rust 标准库实现信道的方式意味着一个信道可以有多个产生值的 <strong>发送</strong><em>sending</em>)端,但只能有一个消费这些值的 <strong>接收</strong><em>receiving</em>)端。想象一下多条小河小溪最终汇聚成大河:所有通过这些小河发出的东西最后都会来到下游的大河。目前我们以单个生产者开始,但是当示例可以工作后会增加多个生产者。</p>
<p><code>mpsc::channel</code> 函数返回一个元组:第一个元素是发送端 -- 发送者,而第二个元素是接收端 -- 接收者。由于历史原因,<code>tx</code><code>rx</code> 通常作为 <strong>发送者</strong><em>transmitter</em>)和 <strong>接收者</strong><em>receiver</em>)的缩写,所以这就是我们将用来绑定这两端变量的名字。这里使用了一个 <code>let</code> 语句和模式来解构了此元组;第十九章会讨论 <code>let</code> 语句中的模式和解构。现在只需知道使用 <code>let</code> 语句是一个方便提取 <code>mpsc::channel</code> 返回的元组中一部分的手段。</p>
<p>让我们将发送端移动到一个新建线程中并发送一个字符串,这样新建线程就可以和主线程通讯了,如示例 16-7 所示。这类似于在河的上游扔下一只橡皮鸭或从一个线程向另一个线程发送聊天信息:</p>
<p><span class="filename">文件名src/main.rs</span></p>
<pre><pre class="playground"><code class="language-rust edition2021">use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let val = String::from("hi");
tx.send(val).unwrap();
});
}</code></pre></pre>
<p><span class="caption">示例 16-7: 将 <code>tx</code> 移动到一个新建的线程中并发送 “hi”</span></p>
<p>这里再次使用 <code>thread::spawn</code> 来创建一个新线程并使用 <code>move</code><code>tx</code> 移动到闭包中这样新建线程就拥有 <code>tx</code> 了。新建线程需要拥有信道的发送端以便能向信道发送消息。信道的发送端有一个 <code>send</code> 方法用来获取需要放入信道的值。<code>send</code> 方法返回一个 <code>Result&lt;T, E&gt;</code> 类型,所以如果接收端已经被丢弃了,将没有发送值的目标,所以发送操作会返回错误。在这个例子中,出错的时候调用 <code>unwrap</code> 产生 panic。不过对于一个真实程序需要合理地处理它回到第九章复习正确处理错误的策略。</p>
<p>在示例 16-8 中,我们在主线程中从信道的接收者获取值。这类似于在河的下游捞起橡皮鸭或接收聊天信息:</p>
<p><span class="filename">文件名src/main.rs</span></p>
<pre><pre class="playground"><code class="language-rust edition2021">use std::sync::mpsc;
use std::thread;
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></pre>
<p><span class="caption">示例 16-8: 在主线程中接收并打印内容 “hi”</span></p>
<p>信道的接收者有两个有用的方法:<code>recv</code><code>try_recv</code>。这里,我们使用了 <code>recv</code>,它是 <em>receive</em> 的缩写。这个方法会阻塞主线程执行直到从信道中接收一个值。一旦发送了一个值,<code>recv</code> 会在一个 <code>Result&lt;T, E&gt;</code> 中返回它。当信道发送端关闭,<code>recv</code> 会返回一个错误表明不会再有新的值到来了。</p>
<p><code>try_recv</code> 不会阻塞,相反它立刻返回一个 <code>Result&lt;T, E&gt;</code><code>Ok</code> 值包含可用的信息,而 <code>Err</code> 值代表此时没有任何消息。如果线程在等待消息过程中还有其他工作时使用 <code>try_recv</code> 很有用:可以编写一个循环来频繁调用 <code>try_recv</code>,在有可用消息时进行处理,其余时候则处理一会其他工作直到再次检查。</p>
<p>出于简单的考虑,这个例子使用了 <code>recv</code>;主线程中除了等待消息之外没有任何其他工作,所以阻塞主线程是合适的。</p>
<p>如果运行示例 16-8 中的代码,我们将会看到主线程打印出这个值:</p>
<pre><code class="language-text">Got: hi
</code></pre>
<p>完美!</p>
<h3 id="信道与所有权转移"><a class="header" href="#信道与所有权转移">信道与所有权转移</a></h3>
<p>所有权规则在消息传递中扮演了重要角色,其有助于我们编写安全的并发代码。防止并发编程中的错误是在 Rust 程序中考虑所有权的一大优势。现在让我们做一个试验来看看信道与所有权如何一同协作以避免产生问题:我们将尝试在新建线程中的信道中发送完 <code>val</code><strong>之后</strong> 再使用它。尝试编译示例 16-9 中的代码并看看为何这是不允许的:</p>
<p><span class="filename">文件名src/main.rs</span></p>
<pre><code class="language-rust ignore does_not_compile">use std::sync::mpsc;
use std::thread;
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">示例 16-9: 在我们已经发送到信道中后,尝试使用 <code>val</code> 引用</span></p>
<p>这里尝试在通过 <code>tx.send</code> 发送 <code>val</code> 到信道中之后将其打印出来。允许这么做是一个坏主意:一旦将值发送到另一个线程后,那个线程可能会在我们再次使用它之前就将其修改或者丢弃。其他线程对值可能的修改会由于不一致或不存在的数据而导致错误或意外的结果。然而,尝试编译示例 16-9 的代码时Rust 会给出一个错误:</p>
<pre><code class="language-console">$ cargo run
Compiling message-passing v0.1.0 (file:///projects/message-passing)
error[E0382]: borrow of moved value: `val`
--&gt; src/main.rs:10:26
|
8 | let val = String::from("hi");
| --- move occurs because `val` has type `String`, which does not implement the `Copy` trait
9 | tx.send(val).unwrap();
| --- value moved here
10 | println!("val is {val}");
| ^^^^^ value borrowed here after move
|
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
For more information about this error, try `rustc --explain E0382`.
error: could not compile `message-passing` (bin "message-passing") due to 1 previous error
</code></pre>
<p>我们的并发错误会造成一个编译时错误。<code>send</code> 函数获取其参数的所有权并移动这个值归接收者所有。这可以防止在发送后再次意外地使用这个值;所有权系统检查一切是否合乎规则。</p>
<h3 id="发送多个值并观察接收者的等待"><a class="header" href="#发送多个值并观察接收者的等待">发送多个值并观察接收者的等待</a></h3>
<p>示例 16-8 中的代码可以编译和运行,不过它并没有明确的告诉我们两个独立的线程通过信道相互通讯。示例 16-10 则有一些改进会证明示例 16-8 中的代码是并发执行的:新建线程现在会发送多个消息并在每个消息之间暂停一秒钟。</p>
<p><span class="filename">文件名src/main.rs</span></p>
<pre><code class="language-rust noplayground">use std::sync::mpsc;
use std::thread;
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::from_secs(1));
}
});
for received in rx {
println!("Got: {received}");
}
}</code></pre>
<p><span class="caption">示例 16-10: 发送多个消息,并在每次发送后暂停一段时间</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 class="language-text">Got: hi
Got: from
Got: the
Got: thread
</code></pre>
<p>因为主线程中的 <code>for</code> 循环里并没有任何暂停或等待的代码,所以可以说主线程是在等待从新建线程中接收值。</p>
<h3 id="通过克隆发送者来创建多个生产者"><a class="header" href="#通过克隆发送者来创建多个生产者">通过克隆发送者来创建多个生产者</a></h3>
<p>之前我们提到了<code>mpsc</code><em>multiple producer, single consumer</em> 的缩写。可以运用 <code>mpsc</code> 来扩展示例 16-10 中的代码来创建向同一接收者发送值的多个线程。这可以通过克隆发送者来做到,如示例 16-11 所示:</p>
<p><span class="filename">文件名src/main.rs</span></p>
<pre><code class="language-rust noplayground"><span class="boring">use std::sync::mpsc;
</span><span class="boring">use std::thread;
</span><span class="boring">use std::time::Duration;
</span><span class="boring">
</span><span class="boring">fn main() {
</span> // --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::from_secs(1));
}
});
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::from_secs(1));
}
});
for received in rx {
println!("Got: {received}");
}
// --snip--
<span class="boring">}</span></code></pre>
<p><span class="caption">示例 16-11: 从多个生产者发送多个消息</span></p>
<p>这一次,在创建新线程之前,我们对发送者调用了 <code>clone</code> 方法。这会给我们一个可以传递给第一个新建线程的发送端句柄。我们会将原始的信道发送端传递给第二个新建线程。这样就会有两个线程,每个线程将向信道的接收端发送不同的消息。</p>
<p>如果运行这些代码,你 <strong>可能</strong> 会看到这样的输出:</p>
<pre><code class="language-text">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>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="ch16-01-threads.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="ch16-03-shared-state.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="ch16-01-threads.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="ch16-03-shared-state.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script>
window.playground_copyable = true;
</script>
<script src="elasticlunr.min.js"></script>
<script src="mark.min.js"></script>
<script src="searcher.js"></script>
<script src="clipboard.min.js"></script>
<script src="highlight.js"></script>
<script src="book.js"></script>
<!-- Custom JS scripts -->
<script src="ferris.js"></script>
</div>
</body>
</html>