|
|
<!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"><strong>16.2.</strong> 消息传递</a></li><li><a href="ch16-03-shared-state.html" class="active"><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><li><a href="ch17-00-oop.html"><strong>17.</strong> 面向对象</a></li><li><ul class="section"><li><a href="ch17-01-what-is-oo.html"><strong>17.1.</strong> 什么是面向对象?</a></li><li><a href="ch17-02-trait-objects.html"><strong>17.2.</strong> 为使用不同类型的值而设计的 trait 对象</a></li><li><a href="ch17-03-oo-design-patterns.html"><strong>17.3.</strong> 面向对象设计模式的实现</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-03-shared-state.md">ch16-03-shared-state.md</a>
|
|
|
<br>
|
|
|
commit 9df612e93e038b05fc959db393c15a5402033f47</p>
|
|
|
</blockquote>
|
|
|
<p>虽然消息传递是一个很好的处理并发的方式,但并不是唯一的一个。再次考虑一下它的口号:</p>
|
|
|
<blockquote>
|
|
|
<p>Do not communicate by sharing memory; instead, share memory by
|
|
|
communicating.</p>
|
|
|
<p>不要共享内存来通讯;而是要通讯来共享内存。</p>
|
|
|
</blockquote>
|
|
|
<p>那么“共享内存来通讯”是怎样的呢?共享内存并发有点像多所有权:多个线程可以同时访问相同的内存位置。第十五章介绍了智能指针如何使得多所有权成为可能,然而这会增加额外的复杂性,因为需要以某种方式管理这些不同的所有者。</p>
|
|
|
<p>不过 Rust 的类型系统和所有权可以很好的帮助我们,正确的管理它们。以共享内存中更常见的并发原语:互斥器(mutexes)为例,让我们看看具体的情况。</p>
|
|
|
<a class="header" href="#互斥器一次只允许一个线程访问数据" name="互斥器一次只允许一个线程访问数据"><h3>互斥器一次只允许一个线程访问数据</h3></a>
|
|
|
<p><strong>互斥器</strong>(<em>mutex</em>)是一种用于共享内存的并发原语。它是“mutual exclusion”的缩写,也就是说,任意时间,它只允许一个线程访问某些数据。互斥器以难以使用著称,因为你不得不记住:</p>
|
|
|
<ol>
|
|
|
<li>在使用数据之前尝试获取锁。</li>
|
|
|
<li>处理完被互斥器所保护的数据之后,必须解锁数据,这样其他线程才能够获取锁。</li>
|
|
|
</ol>
|
|
|
<p>现实中也有互斥器的例子,想象一下在一个会议中,只有一个麦克风。如果一个成员要发言,他必须请求使用麦克风。一旦得到了麦克风,他可以畅所欲言,然后将麦克风交给下一个希望讲话的成员。如果成员在没有麦克风的时候就开始叫喊,或者在其他成员发言结束之前就拿走麦克风,是很不合适的。如果这个共享的麦克风因为此类原因而出现问题,会议将无法正常进行。</p>
|
|
|
<p>正确的管理互斥器异常复杂,这也是许多人之所以热衷于通道的原因。然而,在 Rust 中,得益于类型系统和所有权,我们不会在锁和解锁上出错。</p>
|
|
|
<a class="header" href="#mutext的-api" name="mutext的-api"><h3><code>Mutex<T></code>的 API</h3></a>
|
|
|
<p>让我们看看列表 16-12 中使用互斥器的例子,现在不涉及多线程:</p>
|
|
|
<p><span class="filename">Filename: src/main.rs</span></p>
|
|
|
<pre><code class="language-rust">use std::sync::Mutex;
|
|
|
|
|
|
fn main() {
|
|
|
let m = Mutex::new(5);
|
|
|
|
|
|
{
|
|
|
let mut num = m.lock().unwrap();
|
|
|
*num = 6;
|
|
|
}
|
|
|
|
|
|
println!("m = {:?}", m);
|
|
|
}
|
|
|
</code></pre>
|
|
|
<p><span class="caption">Listing 16-12: Exploring the API of <code>Mutex<T></code> in a
|
|
|
single threaded context for simplicity</span></p>
|
|
|
<p>像很多类型一样,我们使用关联函数 <code>new</code> 来创建一个 <code>Mutex<T></code>。使用<code>lock</code>方法获取锁,以访问互斥器中的数据。这个调用会阻塞,直到我们拥有锁为止。如果另一个线程拥有锁,并且那个线程 panic 了,则这个调用会失败。类似于列表 16-6 那样,我们暂时使用 <code>unwrap()</code> 进行错误处理,或者使用第九章中提及的更好的工具。</p>
|
|
|
<p>一旦获取了锁,就可以将返回值(在这里是<code>num</code>)作为一个数据的可变引用使用了。观察 Rust 类型系统如何保证使用值之前必须获取锁:<code>Mutex<i32></code>并不是一个<code>i32</code>,所以<strong>必须</strong>获取锁才能使用这个<code>i32</code>值。我们是不会忘记这么做的,因为类型系统不允许。</p>
|
|
|
<p>你也许会怀疑,<code>Mutex<T></code>是一个智能指针?是的!更准确的说,<code>lock</code>调用返回一个叫做<code>MutexGuard</code>的智能指针。类似我们在第十五章见过的智能指针,它实现了<code>Deref</code>来指向其内部数据。另外<code>MutexGuard</code>有一个用来释放锁的<code>Drop</code>实现。这样就不会忘记释放锁了。这在<code>MutexGuard</code>离开作用域时会自动发生,例如它发生于列表 16-12 中内部作用域的结尾。接着可以打印出互斥器的值并发现能够将其内部的<code>i32</code>改为 6。</p>
|
|
|
<a class="header" href="#在线程间共享mutext" name="在线程间共享mutext"><h4>在线程间共享<code>Mutex<T></code></h4></a>
|
|
|
<p>现在让我们尝试使用<code>Mutex<T></code>在多个线程间共享值。我们将启动十个线程,并在各个线程中对同一个计数器值加一,这样计数器将从 0 变为 10。注意,接下来的几个例子会出现编译错误,而我们将通过这些错误来学习如何使用
|
|
|
<code>Mutex<T></code>,以及 Rust 又是如何辅助我们以确保正确。列表 16-13 是最开始的例子:</p>
|
|
|
<p><span class="filename">Filename: src/main.rs</span></p>
|
|
|
<pre><code class="language-rust,ignore">use std::sync::Mutex;
|
|
|
use std::thread;
|
|
|
|
|
|
fn main() {
|
|
|
let counter = Mutex::new(0);
|
|
|
let mut handles = vec![];
|
|
|
|
|
|
for _ in 0..10 {
|
|
|
let handle = thread::spawn(|| {
|
|
|
let mut num = counter.lock().unwrap();
|
|
|
|
|
|
*num += 1;
|
|
|
});
|
|
|
handles.push(handle);
|
|
|
}
|
|
|
|
|
|
for handle in handles {
|
|
|
handle.join().unwrap();
|
|
|
}
|
|
|
|
|
|
println!("Result: {}", *counter.lock().unwrap());
|
|
|
}
|
|
|
</code></pre>
|
|
|
<p><span class="caption">Listing 16-13: The start of a program having 10 threads
|
|
|
each increment a counter guarded by a <code>Mutex<T></code></span></p>
|
|
|
<p>这里创建了一个 <code>counter</code> 变量来存放内含 <code>i32</code> 的 <code>Mutex<T></code>,类似列表 16-12 那样。接下来使用 range 创建了 10 个线程。使用了 <code>thread::spawn</code> 并对所有线程使用了相同的闭包:他们每一个都将调用 <code>lock</code> 方法来获取 <code>Mutex<T></code> 上的锁,接着将互斥器中的值加一。当一个线程结束执行,<code>num</code> 会离开闭包作用域并释放锁,这样另一个线程就可以获取它了。</p>
|
|
|
<p>在主线程中,我们像列表 16-2 那样收集了所有的 join 句柄,调用它们的 <code>join</code> 方法来确保所有线程都会结束。之后,主线程会获取锁并打印出程序的结果。</p>
|
|
|
<p>之前提示过这个例子不能编译,让我们看看为什么!</p>
|
|
|
<pre><code>error[E0373]: closure may outlive the current function, but it borrows
|
|
|
`counter`, which is owned by the current function
|
|
|
-->
|
|
|
|
|
|
|
9 | let handle = thread::spawn(|| {
|
|
|
| ^^ may outlive borrowed value `counter`
|
|
|
10 | let mut num = counter.lock().unwrap();
|
|
|
| ------- `counter` is borrowed here
|
|
|
|
|
|
|
help: to force the closure to take ownership of `counter` (and any other
|
|
|
referenced variables), use the `move` keyword, as shown:
|
|
|
| let handle = thread::spawn(move || {
|
|
|
</code></pre>
|
|
|
<p>这类似于列表 16-5 中解决了的问题。考虑到启动了多个线程,Rust 无法知道这些线程会运行多久,而在每一个线程尝试借用 <code>counter</code> 时它是否仍然有效。帮助信息提醒了我们如何解决它:可以使用 <code>move</code> 来给予每个线程其所有权。尝试在闭包上做一点改动:</p>
|
|
|
<pre><code class="language-rust,ignore">thread::spawn(move || {
|
|
|
</code></pre>
|
|
|
<p>再次编译。这回出现了一个不同的错误!</p>
|
|
|
<pre><code>error[E0382]: capture of moved value: `counter`
|
|
|
-->
|
|
|
|
|
|
|
9 | let handle = thread::spawn(move || {
|
|
|
| ------- value moved (into closure) here
|
|
|
10 | let mut num = counter.lock().unwrap();
|
|
|
| ^^^^^^^ value captured here after move
|
|
|
|
|
|
|
= note: move occurs because `counter` has type `std::sync::Mutex<i32>`,
|
|
|
which does not implement the `Copy` trait
|
|
|
|
|
|
error[E0382]: use of moved value: `counter`
|
|
|
-->
|
|
|
|
|
|
|
9 | let handle = thread::spawn(move || {
|
|
|
| ------- value moved (into closure) here
|
|
|
...
|
|
|
21 | println!("Result: {}", *counter.lock().unwrap());
|
|
|
| ^^^^^^^ value used here after move
|
|
|
|
|
|
|
= note: move occurs because `counter` has type `std::sync::Mutex<i32>`,
|
|
|
which does not implement the `Copy` trait
|
|
|
|
|
|
error: aborting due to 2 previous errors
|
|
|
</code></pre>
|
|
|
<p><code>move</code> 并没有像列表 16-5 中那样解决问题。为什么呢?错误信息有点难懂,因为它表明 <code>counter</code> 被移动进了闭包,接着它在调用 <code>lock</code> 时被捕获。这似乎是我们希望的,然而不被允许。</p>
|
|
|
<p>让我们推理一下。这次不再使用 <code>for</code> 循环创建 10 个线程,只创建两个线程,看看会发生什么。将列表 16-13 中第一个<code>for</code>循环替换为如下代码:</p>
|
|
|
<pre><code class="language-rust,ignore">let handle = thread::spawn(move || {
|
|
|
let mut num = counter.lock().unwrap();
|
|
|
|
|
|
*num += 1;
|
|
|
});
|
|
|
handles.push(handle);
|
|
|
|
|
|
let handle2 = thread::spawn(move || {
|
|
|
let mut num2 = counter.lock().unwrap();
|
|
|
|
|
|
*num2 += 1;
|
|
|
});
|
|
|
handles.push(handle2);
|
|
|
</code></pre>
|
|
|
<p>这里创建了两个线程,并将第二个线程所用的变量改名为 <code>handle2</code> 和 <code>num2</code>。我们简化了例子,看是否能理解错误信息。此次编译给出如下信息:</p>
|
|
|
<pre><code class="language-text">error[E0382]: capture of moved value: `counter`
|
|
|
-->
|
|
|
|
|
|
|
8 | let handle = thread::spawn(move || {
|
|
|
| ------- value moved (into closure) here
|
|
|
...
|
|
|
16 | let mut num = counter.lock().unwrap();
|
|
|
| ^^^^^^^ value captured here after move
|
|
|
|
|
|
|
= note: move occurs because `counter` has type `std::sync::Mutex<i32>`,
|
|
|
which does not implement the `Copy` trait
|
|
|
|
|
|
error[E0382]: use of moved value: `counter`
|
|
|
-->
|
|
|
|
|
|
|
8 | let handle = thread::spawn(move || {
|
|
|
| ------- value moved (into closure) here
|
|
|
...
|
|
|
26 | println!("Result: {}", *counter.lock().unwrap());
|
|
|
| ^^^^^^^ value used here after move
|
|
|
|
|
|
|
= note: move occurs because `counter` has type `std::sync::Mutex<i32>`,
|
|
|
which does not implement the `Copy` trait
|
|
|
|
|
|
error: aborting due to 2 previous errors
|
|
|
</code></pre>
|
|
|
<p>啊哈!第一个错误信息中说,<code>counter</code> 被移动进了 <code>handle</code> 所代表线程的闭包中。因此我们无法在第二个线程中对其调用 <code>lock</code>,并将结果储存在 <code>num2</code> 中时捕获<code>counter</code>!所以 Rust 告诉我们不能将 <code>counter</code> 的所有权移动到多个线程中。这在之前很难看出,因为我们在循环中创建了多个线程,而 Rust 无法在每次迭代中指明不同的线程(没有临时变量 <code>num2</code>)。</p>
|
|
|
<a class="header" href="#多线程和多所有权" name="多线程和多所有权"><h4>多线程和多所有权</h4></a>
|
|
|
<p>在第十五章中,我们通过使用智能指针 <code>Rc<T></code> 来创建引用计数的值,以便拥有多所有权。同时第十五章提到了 <code>Rc<T></code> 只能在单线程环境中使用,不过还是在这里试用 <code>Rc<T></code> 看看会发生什么。列表 16-14 将 <code>Mutex<T></code> 装进了 <code>Rc<T></code> 中,并在移入线程之前克隆了 <code>Rc<T></code>。再用循环来创建线程,保留闭包中的 <code>move</code> 关键字:</p>
|
|
|
<p><span class="filename">Filename: src/main.rs</span></p>
|
|
|
<pre><code class="language-rust">use std::rc::Rc;
|
|
|
use std::sync::Mutex;
|
|
|
use std::thread;
|
|
|
|
|
|
fn main() {
|
|
|
let counter = Rc::new(Mutex::new(0));
|
|
|
let mut handles = vec![];
|
|
|
|
|
|
for _ in 0..10 {
|
|
|
let counter = counter.clone();
|
|
|
let handle = thread::spawn(move || {
|
|
|
let mut num = counter.lock().unwrap();
|
|
|
|
|
|
*num += 1;
|
|
|
});
|
|
|
handles.push(handle);
|
|
|
}
|
|
|
|
|
|
for handle in handles {
|
|
|
handle.join().unwrap();
|
|
|
}
|
|
|
|
|
|
println!("Result: {}", *counter.lock().unwrap());
|
|
|
}
|
|
|
</code></pre>
|
|
|
<p><span class="caption">Listing 16-14: Attempting to use <code>Rc<T></code> to allow
|
|
|
multiple threads to own the <code>Mutex<T></code></span></p>
|
|
|
<p>再一次编译并...出现了不同的错误!编译器真是教会了我们很多!</p>
|
|
|
<pre><code>error[E0277]: the trait bound `std::rc::Rc<std::sync::Mutex<i32>>:
|
|
|
std::marker::Send` is not satisfied
|
|
|
-->
|
|
|
|
|
|
|
11 | let handle = thread::spawn(move || {
|
|
|
| ^^^^^^^^^^^^^ the trait `std::marker::Send` is not
|
|
|
implemented for `std::rc::Rc<std::sync::Mutex<i32>>`
|
|
|
|
|
|
|
= note: `std::rc::Rc<std::sync::Mutex<i32>>` cannot be sent between threads
|
|
|
safely
|
|
|
= note: required because it appears within the type
|
|
|
`[closure@src/main.rs:11:36: 15:10
|
|
|
counter:std::rc::Rc<std::sync::Mutex<i32>>]`
|
|
|
= note: required by `std::thread::spawn`
|
|
|
</code></pre>
|
|
|
<p>哇哦,太长不看!说重点:第一个提示表明 <code>Rc<Mutex<i32>></code> 不能安全的在线程间传递。理由也在错误信息中,“不满足 <code>Send</code> trait bound”(<code>the trait bound Send is not satisfied</code>)。下一部分将会讨论 <code>Send</code>,它是确保许多用在多线程中的类型,能够适合并发环境的 trait 之一。</p>
|
|
|
<p>不幸的是,<code>Rc<T></code> 并不能安全的在线程间共享。当 <code>Rc<T></code> 管理引用计数时,它必须在每一个 <code>clone</code> 调用时增加计数,并在每一个克隆被丢弃时减少计数。<code>Rc<T></code> 并没有使用任何并发原语,来确保改变计数的操作不会被其他线程打断。在计数出错时可能会导致诡异的 bug,比如可能会造成内存泄漏,或在使用结束之前就丢弃一个值。如果有一个类型与 <code>Rc<T></code> 相似,又以一种线程安全的方式改变引用计数,会怎么样呢?</p>
|
|
|
<a class="header" href="#原子引用计数-arct" name="原子引用计数-arct"><h4>原子引用计数 <code>Arc<T></code></h4></a>
|
|
|
<p>答案是肯定的,确实有一个类似<code>Rc<T></code>并可以安全的用于并发环境的类型:<code>Arc<T></code>。字母“a”代表<strong>原子性</strong>(<em>atomic</em>),所以这是一个<strong>原子引用计数</strong>(<em>atomically reference counted</em>)类型。原子性是另一类这里还未涉及到的并发原语;请查看标准库中<code>std::sync::atomic</code>的文档来获取更多细节。其中的要点就是:原子性类型工作起来类似原始类型,不过可以安全的在线程间共享。</p>
|
|
|
<p>为什么不是所有的原始类型都是原子性的?为什么不是所有标准库中的类型都默认使用<code>Arc<T></code>实现?线程安全带来性能惩罚,我们希望只在必要时才为此买单。如果只是在单线程中对值进行操作,原子性提供的保证并无必要,代码可以因此运行的更快。</p>
|
|
|
<p>回到之前的例子:<code>Arc<T></code>和<code>Rc<T></code>除了<code>Arc<T></code>内部的原子性之外没有区别。其 API 也相同,所以可以修改<code>use</code>行和<code>new</code>调用。列表 16-15 中的代码最终可以编译和运行:</p>
|
|
|
<p><span class="filename">Filename: src/main.rs</span></p>
|
|
|
<pre><code class="language-rust">use std::sync::{Mutex, Arc};
|
|
|
use std::thread;
|
|
|
|
|
|
fn main() {
|
|
|
let counter = Arc::new(Mutex::new(0));
|
|
|
let mut handles = vec![];
|
|
|
|
|
|
for _ in 0..10 {
|
|
|
let counter = counter.clone();
|
|
|
let handle = thread::spawn(move || {
|
|
|
let mut num = counter.lock().unwrap();
|
|
|
|
|
|
*num += 1;
|
|
|
});
|
|
|
handles.push(handle);
|
|
|
}
|
|
|
|
|
|
for handle in handles {
|
|
|
handle.join().unwrap();
|
|
|
}
|
|
|
|
|
|
println!("Result: {}", *counter.lock().unwrap());
|
|
|
}
|
|
|
</code></pre>
|
|
|
<p><span class="caption">Listing 16-15: Using an <code>Arc<T></code> to wrap the <code>Mutex<T></code>
|
|
|
to be able to share ownership across multiple threads</span></p>
|
|
|
<p>这会打印出:</p>
|
|
|
<pre><code>Result: 10
|
|
|
</code></pre>
|
|
|
<p>成功了!我们从 0 数到了 10,这可能并不是很显眼,不过一路上我们学习了很多关于<code>Mutex<T></code>和线程安全的内容!这个例子中构建的结构可以用于比增加计数更为复杂的操作。能够被分解为独立部分的计算可以像这样被分散到多个线程中,并可以使用<code>Mutex<T></code>来允许每个线程在他们自己的部分更新最终的结果。</p>
|
|
|
<p>你可能注意到了,因为<code>counter</code>是不可变的,不过可以获取其内部值的可变引用,这意味着<code>Mutex<T></code>提供了内部可变性,就像<code>Cell</code>系列类型那样。正如第十五章中使用<code>RefCell<T></code>可以改变<code>Rc<T></code>中的内容那样,同样的可以使用<code>Mutex<T></code>来改变<code>Arc<T></code>中的内容。</p>
|
|
|
<p>回忆一下<code>Rc<T></code>并没有避免所有可能的问题:我们也讨论了当两个<code>Rc<T></code>相互引用时的引用循环的可能性,这可能造成内存泄露。<code>Mutex<T></code>有一个类似的 Rust 同样也不能避免的问题:死锁。<strong>死锁</strong>(<em>deadlock</em>)是一个场景中操作需要锁定两个资源,而两个线程分别拥有一个锁并永远相互等待的问题。如果你对这个主题感兴趣,尝试编写一个带有死锁的 Rust 程序,接着研究任何其他语言中使用互斥器的死锁规避策略并尝试在 Rust 中实现他们。标准库中<code>Mutex<T></code>和<code>MutexGuard</code>的 API 文档会提供有用的信息。</p>
|
|
|
<p>Rust 的类型系统和所有权规则,确保了线程在更新共享值时拥有独占的访问权限,所以线程不会以不可预测的方式覆盖彼此的操作。虽然为了使一切正确运行而在编译器上花了一些时间,但是我们节省了未来的时间,尤其是线程以特定顺序执行才会出现的诡异错误难以重现。</p>
|
|
|
<p>接下来,为了丰富本章的内容,让我们讨论一下<code>Send</code>和<code>Sync</code> trait 以及如何对自定义类型使用他们。</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<!-- Mobile navigation buttons -->
|
|
|
|
|
|
<a href="ch16-02-message-passing.html" class="mobile-nav-chapters previous">
|
|
|
<i class="fa fa-angle-left"></i>
|
|
|
</a>
|
|
|
|
|
|
|
|
|
|
|
|
<a href="ch16-04-extensible-concurrency-sync-and-send.html" class="mobile-nav-chapters next">
|
|
|
<i class="fa fa-angle-right"></i>
|
|
|
</a>
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
<a href="ch16-02-message-passing.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-04-extensible-concurrency-sync-and-send.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>
|