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.

368 lines
22 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!DOCTYPE HTML>
<html lang="zh-CN" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>基于 Send 和 Sync 的线程安全 - Rust语言圣经(Rust Course)</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" id="highlight-css" href="../../highlight.css">
<link rel="stylesheet" id="tomorrow-night-css" href="../../tomorrow-night.css">
<link rel="stylesheet" id="ayu-highlight-css" href="../../ayu-highlight.css">
<!-- Custom theme stylesheets -->
<link rel="stylesheet" href="../../theme/style.css">
<!-- Provide site root and default themes to javascript -->
<script>
const path_to_root = "../../";
const default_light_theme = "light";
const default_dark_theme = "navy";
</script>
<!-- Start loading toc.js asap -->
<script src="../../toc.js"></script>
</head>
<body>
<div id="body-container">
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
let theme = localStorage.getItem('mdbook-theme');
let 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>
const default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? default_dark_theme : default_light_theme;
let theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
const html = document.documentElement;
html.classList.remove('light')
html.classList.add(theme);
html.classList.add("js");
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
let sidebar = null;
const 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';
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<!-- populated by js -->
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
<noscript>
<iframe class="sidebar-iframe-outer" src="../../toc.html"></iframe>
</noscript>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<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="default_theme">Auto</button></li>
<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语言圣经(Rust Course)</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/sunface/rust-course" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa fa-github"></i>
</a>
<a href="https://github.com/sunface/rust-course/edit/main/src/advance/concurrency-with-threads/send-sync.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>
<h1 id="基于-send-和-sync-的线程安全"><a class="header" href="#基于-send-和-sync-的线程安全">基于 Send 和 Sync 的线程安全</a></h1>
<p>为何 Rc、RefCell 和裸指针不可以在多线程间使用?如何让裸指针可以在多线程使用?我们一起来探寻下这些问题的答案。</p>
<h2 id="无法用于多线程的rc"><a class="header" href="#无法用于多线程的rc">无法用于多线程的<code>Rc</code></a></h2>
<p>先来看一段多线程使用<code>Rc</code>的代码:</p>
<pre><pre class="playground"><code class="language-rust edition2021">use std::thread;
use std::rc::Rc;
fn main() {
let v = Rc::new(5);
let t = thread::spawn(move || {
println!("{}",v);
});
t.join().unwrap();
}</code></pre></pre>
<p>以上代码将<code>v</code>的所有权通过<code>move</code>转移到子线程中,看似正确实则会报错:</p>
<pre><code class="language-console">error[E0277]: `Rc&lt;i32&gt;` cannot be sent between threads safely
------ 省略部分报错 --------
= help: within `[closure@src/main.rs:5:27: 7:6]`, the trait `Send` is not implemented for `Rc&lt;i32&gt;`
</code></pre>
<p>表面原因是<code>Rc</code>无法在线程间安全的转移,实际是编译器给予我们的那句帮助: <code>the trait `Send` is not implemented for `Rc&lt;i32&gt;` </code>(<code>Rc&lt;i32&gt;</code>未实现<code>Send</code>特征), 那么此处的<code>Send</code>特征又是何方神圣?</p>
<h2 id="rc-和-arc-源码对比"><a class="header" href="#rc-和-arc-源码对比">Rc 和 Arc 源码对比</a></h2>
<p>在介绍<code>Send</code>特征之前,再来看看<code>Arc</code>为何可以在多线程使用,玄机在于两者的源码实现上:</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// Rc源码片段
impl&lt;T: ?Sized&gt; !marker::Send for Rc&lt;T&gt; {}
impl&lt;T: ?Sized&gt; !marker::Sync for Rc&lt;T&gt; {}
// Arc源码片段
unsafe impl&lt;T: ?Sized + Sync + Send&gt; Send for Arc&lt;T&gt; {}
unsafe impl&lt;T: ?Sized + Sync + Send&gt; Sync for Arc&lt;T&gt; {}
<span class="boring">}</span></code></pre></pre>
<p><code>!</code>代表移除特征的相应实现,上面代码中<code>Rc&lt;T&gt;</code><code>Send</code><code>Sync</code>特征被特地移除了实现,而<code>Arc&lt;T&gt;</code>则相反,实现了<code>Sync + Send</code>,再结合之前的编译器报错,大概可以明白了:<code>Send</code><code>Sync</code>是在线程间安全使用一个值的关键。</p>
<h2 id="send-和-sync"><a class="header" href="#send-和-sync">Send 和 Sync</a></h2>
<p><code>Send</code><code>Sync</code>是 Rust 安全并发的重中之重,但是实际上它们只是标记特征(marker trait该特征未定义任何行为因此非常适合用于标记), 来看看它们的作用:</p>
<ul>
<li>实现<code>Send</code>的类型可以在线程间安全的传递其所有权</li>
<li>实现<code>Sync</code>的类型可以在线程间安全的共享(通过引用)</li>
</ul>
<p>这里还有一个潜在的依赖:一个类型要在线程间安全的共享的前提是,指向它的引用必须能在线程间传递。因为如果引用都不能被传递,我们就无法在多个线程间使用引用去访问同一个数据了。</p>
<p>由上可知,<strong>若类型 T 的引用<code>&amp;T</code><code>Send</code>,则<code>T</code><code>Sync</code></strong></p>
<p>没有例子的概念讲解都是耍流氓,来看看<code>RwLock</code>的实现:</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>unsafe impl&lt;T: ?Sized + Send + Sync&gt; Sync for RwLock&lt;T&gt; {}
<span class="boring">}</span></code></pre></pre>
<p>首先<code>RwLock</code>可以在线程间安全的共享,那它肯定是实现了<code>Sync</code>,但是我们的关注点不在这里。众所周知,<code>RwLock</code>可以并发的读,说明其中的值<code>T</code>必定也可以在线程间共享,那<code>T</code>必定要实现<code>Sync</code></p>
<p>果不其然,上述代码中,<code>T</code>的特征约束中就有一个<code>Sync</code>特征,那问题又来了,<code>Mutex</code>是不是相反?再来看看:</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>unsafe impl&lt;T: ?Sized + Send&gt; Sync for Mutex&lt;T&gt; {}
<span class="boring">}</span></code></pre></pre>
<p>不出所料,<code>Mutex&lt;T&gt;</code>中的<code>T</code>并没有<code>Sync</code>特征约束。</p>
<p>武学秘籍再好,不见生死也是花拳绣腿。同样的,我们需要通过实战来彻底掌握<code>Send</code><code>Sync</code>,但在实战之前,先来简单看看有哪些类型实现了它们。</p>
<h2 id="实现send和sync的类型"><a class="header" href="#实现send和sync的类型">实现<code>Send</code><code>Sync</code>的类型</a></h2>
<p>在 Rust 中,几乎所有类型都默认实现了<code>Send</code><code>Sync</code>,而且由于这两个特征都是可自动派生的特征(通过<code>derive</code>派生),意味着一个复合类型(例如结构体), 只要它内部的所有成员都实现了<code>Send</code>或者<code>Sync</code>,那么它就自动实现了<code>Send</code><code>Sync</code></p>
<p>正是因为以上规则Rust 中绝大多数类型都实现了<code>Send</code><code>Sync</code>,除了以下几个(事实上不止这几个,只不过它们比较常见):</p>
<ul>
<li>裸指针两者都没实现,因为它本身就没有任何安全保证</li>
<li><code>UnsafeCell</code>不是<code>Sync</code>,因此<code>Cell</code><code>RefCell</code>也不是</li>
<li><code>Rc</code>两者都没实现(因为内部的引用计数器不是线程安全的)</li>
</ul>
<p>当然,如果是自定义的复合类型,那没实现那哥俩的就较为常见了:<strong>只要复合类型中有一个成员不是<code>Send</code><code>Sync</code>,那么该复合类型也就不是<code>Send</code><code>Sync</code></strong></p>
<p><strong>手动实现 <code>Send</code><code>Sync</code> 是不安全的</strong>,通常并不需要手动实现 Send 和 Sync trait实现者需要使用<code>unsafe</code>小心维护并发安全保证。</p>
<p>至此,相关的概念大家已经掌握,但是我敢肯定,对于这两个滑不溜秋的家伙,大家依然会非常模糊,不知道它们该如何使用。那么我们来一起看看如何让裸指针可以在线程间安全的使用。</p>
<h2 id="为裸指针实现send"><a class="header" href="#为裸指针实现send">为裸指针实现<code>Send</code></a></h2>
<p>上面我们提到裸指针既没实现<code>Send</code>,意味着下面代码会报错:</p>
<pre><pre class="playground"><code class="language-rust edition2021">use std::thread;
fn main() {
let p = 5 as *mut u8;
let t = thread::spawn(move || {
println!("{:?}",p);
});
t.join().unwrap();
}</code></pre></pre>
<p>报错跟之前无二: <code> `*mut u8` cannot be sent between threads safely</code>, 但是有一个问题,我们无法为其直接实现<code>Send</code>特征,好在可以用<a href="https://course.rs/advance/into-types/custom-type.html#newtype"><code>newtype</code>类型</a> :<code>struct MyBox(*mut u8);</code></p>
<p>还记得之前的规则吗:复合类型中有一个成员没实现<code>Send</code>,该复合类型就不是<code>Send</code>,因此我们需要手动为它实现:</p>
<pre><pre class="playground"><code class="language-rust edition2021">use std::thread;
#[derive(Debug)]
struct MyBox(*mut u8);
unsafe impl Send for MyBox {}
fn main() {
let p = MyBox(5 as *mut u8);
let t = thread::spawn(move || {
println!("{:?}",p);
});
t.join().unwrap();
}</code></pre></pre>
<p>此时,我们的指针已经可以欢快的在多线程间撒欢,以上代码很简单,但有一点需要注意:<code>Send</code><code>Sync</code><code>unsafe</code>特征,实现时需要用<code>unsafe</code>代码块包裹。</p>
<h2 id="为裸指针实现sync"><a class="header" href="#为裸指针实现sync">为裸指针实现<code>Sync</code></a></h2>
<p>由于<code>Sync</code>是多线程间共享一个值,大家可能会想这么实现:</p>
<pre><pre class="playground"><code class="language-rust edition2021">use std::thread;
fn main() {
let v = 5;
let t = thread::spawn(|| {
println!("{:?}",&amp;v);
});
t.join().unwrap();
}</code></pre></pre>
<p>关于这种用法,在多线程章节也提到过,线程如果直接去借用其它线程的变量,会报错:<code>closure may outlive the current function,</code>, 原因在于编译器无法确定主线程<code>main</code>和子线程<code>t</code>谁的生命周期更长,特别是当两个线程都是子线程时,没有任何人知道哪个子线程会先结束,包括编译器!</p>
<p>因此我们得配合<code>Arc</code>去使用:</p>
<pre><pre class="playground"><code class="language-rust edition2021">use std::thread;
use std::sync::Arc;
use std::sync::Mutex;
#[derive(Debug)]
struct MyBox(*const u8);
unsafe impl Send for MyBox {}
fn main() {
let b = &amp;MyBox(5 as *const u8);
let v = Arc::new(Mutex::new(b));
let t = thread::spawn(move || {
let _v1 = v.lock().unwrap();
});
t.join().unwrap();
}</code></pre></pre>
<p>上面代码将智能指针<code>v</code>的所有权转移给新线程,同时<code>v</code>包含了一个引用类型<code>b</code>,当在新的线程中试图获取内部的引用时,会报错:</p>
<pre><code class="language-console">error[E0277]: `*const u8` cannot be shared between threads safely
--&gt; src/main.rs:25:13
|
25 | let t = thread::spawn(move || {
| ^^^^^^^^^^^^^ `*const u8` cannot be shared between threads safely
|
= help: within `MyBox`, the trait `Sync` is not implemented for `*const u8`
</code></pre>
<p>因为我们访问的引用实际上还是对主线程中的数据的借用,转移进来的仅仅是外层的智能指针引用。要解决很简单,为<code>MyBox</code>实现<code>Sync</code>:</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>unsafe impl Sync for MyBox {}
<span class="boring">}</span></code></pre></pre>
<h2 id="总结"><a class="header" href="#总结">总结</a></h2>
<p>通过上面的两个裸指针的例子,我们了解了如何实现<code>Send</code><code>Sync</code>,以及如何只实现<code>Send</code>而不实现<code>Sync</code>,简单总结下:</p>
<ol>
<li>实现<code>Send</code>的类型可以在线程间安全的传递其所有权, 实现<code>Sync</code>的类型可以在线程间安全的共享(通过引用)</li>
<li>绝大部分类型都实现了<code>Send</code><code>Sync</code>,常见的未实现的有:裸指针、<code>Cell</code><code>RefCell</code><code>Rc</code></li>
<li>可以为自定义类型实现<code>Send</code><code>Sync</code>,但是需要<code>unsafe</code>代码块</li>
<li>可以为部分 Rust 中的类型实现<code>Send</code><code>Sync</code>,但是需要使用<code>newtype</code>,例如文中的裸指针例子</li>
</ol>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../../advance/concurrency-with-threads/sync2.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="../../advance/global-variable.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="../../advance/concurrency-with-threads/sync2.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="../../advance/global-variable.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="../../ace.js"></script>
<script src="../../editor.js"></script>
<script src="../../mode-rust.js"></script>
<script src="../../theme-dawn.js"></script>
<script src="../../theme-tomorrow_night.js"></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="../../assets/custom2.js"></script>
<script src="../../assets/bigPicture.js"></script>
</div>
</body>
</html>