mirror of
https://github.com/KaiserY/trpl-zh-cn
synced 2024-11-09 08:51:18 +08:00
323 lines
23 KiB
HTML
323 lines
23 KiB
HTML
<!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">
|
||
<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.html"><strong>12.</strong> 一个 I/O 项目</a></li><li><ul class="section"><li><a href="ch12-01-accepting-command-line-arguments.html"><strong>12.1.</strong> 接受命令行参数</a></li><li><a href="ch12-02-reading-a-file.html"><strong>12.2.</strong> 读取文件</a></li><li><a href="ch12-03-improving-error-handling-and-modularity.html"><strong>12.3.</strong> 增强错误处理和模块化</a></li><li><a href="ch12-04-testing-the-librarys-functionality.html"><strong>12.4.</strong> 测试库的功能</a></li><li><a href="ch12-05-working-with-environment-variables.html"><strong>12.5.</strong> 处理环境变量</a></li><li><a href="ch12-06-writing-to-stderr-instead-of-stdout.html"><strong>12.6.</strong> 输出到<code>stderr</code>而不是<code>stdout</code></a></li></ul></li><li><a href="ch13-00-functional-features.html"><strong>13.</strong> Rust 中的函数式语言功能</a></li><li><ul class="section"><li><a href="ch13-01-closures.html"><strong>13.1.</strong> 闭包</a></li><li><a href="ch13-02-iterators.html"><strong>13.2.</strong> 迭代器</a></li><li><a href="ch13-03-improving-our-io-project.html"><strong>13.3.</strong> 改进 I/O 项目</a></li><li><a href="ch13-04-performance.html"><strong>13.4.</strong> 性能</a></li></ul></li><li><a href="ch14-00-more-about-cargo.html"><strong>14.</strong> 更多关于 Cargo 和 Crates.io</a></li><li><ul class="section"><li><a href="ch14-01-release-profiles.html"><strong>14.1.</strong> 发布配置</a></li><li><a href="ch14-02-publishing-to-crates-io.html"><strong>14.2.</strong> 将 crate 发布到 Crates.io</a></li><li><a href="ch14-03-cargo-workspaces.html"><strong>14.3.</strong> Cargo 工作空间</a></li><li><a href="ch14-04-installing-binaries.html"><strong>14.4.</strong> 使用<code>cargo install</code>从 Crates.io 安装文件</a></li><li><a href="ch14-05-extending-cargo.html"><strong>14.5.</strong> Cargo 自定义扩展命令</a></li></ul></li><li><a href="ch15-00-smart-pointers.html"><strong>15.</strong> 智能指针</a></li><li><ul class="section"><li><a href="ch15-01-box.html"><strong>15.1.</strong> <code>Box<T></code>用于已知大小的堆上数据</a></li><li><a href="ch15-02-deref.html"><strong>15.2.</strong> <code>Deref</code> Trait 允许通过引用访问数据</a></li><li><a href="ch15-03-drop.html"><strong>15.3.</strong> <code>Drop</code> Trait 运行清理代码</a></li><li><a href="ch15-04-rc.html"><strong>15.4.</strong> <code>Rc<T></code> 引用计数智能指针</a></li><li><a href="ch15-05-interior-mutability.html"><strong>15.5.</strong> <code>RefCell<T></code>和内部可变性模式</a></li><li><a href="ch15-06-reference-cycles.html"><strong>15.6.</strong> 引用循环和内存泄漏是安全的</a></li></ul></li><li><a href="ch16-00-concurrency.html"><strong>16.</strong> 无畏并发</a></li><li><ul class="section"><li><a href="ch16-01-threads.html"><strong>16.1.</strong> 线程</a></li><li><a href="ch16-02-message-passing.html" class="active"><strong>16.2.</strong> 消息传递</a></li><li><a href="ch16-03-shared-state.html"><strong>16.3.</strong> 共享状态</a></li><li><a href="ch16-04-extensible-concurrency-sync-and-send.html"><strong>16.4.</strong> 可扩展的并发:<code>Sync</code>和<code>Send</code></a></li></ul></li></ul>
|
||
</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">
|
||
<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>
|
||
|
||
</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>
|