RustBook/ch16-01-threads.html

555 lines
42 KiB
HTML
Raw Permalink Normal View History

2020-01-06 20:57:15 +00:00
<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js light">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Using Threads to Run Code Simultaneously - The Rust Programming Language</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
<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 href="googleFonts/css.css" rel="stylesheet" type="text/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">
</head>
<body>
<!-- Provide site root to javascript -->
<script type="text/javascript">
var path_to_root = "";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
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 type="text/javascript">
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('no-js')
html.classList.remove('light')
html.classList.add(theme);
html.classList.add('js');
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
}
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div id="sidebar-scrollbox" class="sidebar-scrollbox">
<ol class="chapter"><li class="expanded affix "><a href="title-page.html">The Rust Programming Language</a></li><li class="expanded affix "><a href="foreword.html">Foreword</a></li><li class="expanded affix "><a href="ch00-00-introduction.html">Introduction</a></li><li class="expanded "><a href="ch01-00-getting-started.html"><strong aria-hidden="true">1.</strong> Getting Started</a></li><li><ol class="section"><li class="expanded "><a href="ch01-01-installation.html"><strong aria-hidden="true">1.1.</strong> Installation</a></li><li class="expanded "><a href="ch01-02-hello-world.html"><strong aria-hidden="true">1.2.</strong> Hello, World!</a></li><li class="expanded "><a href="ch01-03-hello-cargo.html"><strong aria-hidden="true">1.3.</strong> Hello, Cargo!</a></li></ol></li><li class="expanded "><a href="ch02-00-guessing-game-tutorial.html"><strong aria-hidden="true">2.</strong> Programming a Guessing Game</a></li><li class="expanded "><a href="ch03-00-common-programming-concepts.html"><strong aria-hidden="true">3.</strong> Common Programming Concepts</a></li><li><ol class="section"><li class="expanded "><a href="ch03-01-variables-and-mutability.html"><strong aria-hidden="true">3.1.</strong> Variables and Mutability</a></li><li class="expanded "><a href="ch03-02-data-types.html"><strong aria-hidden="true">3.2.</strong> Data Types</a></li><li class="expanded "><a href="ch03-03-how-functions-work.html"><strong aria-hidden="true">3.3.</strong> Functions</a></li><li class="expanded "><a href="ch03-04-comments.html"><strong aria-hidden="true">3.4.</strong> Comments</a></li><li class="expanded "><a href="ch03-05-control-flow.html"><strong aria-hidden="true">3.5.</strong> Control Flow</a></li></ol></li><li class="expanded "><a href="ch04-00-understanding-ownership.html"><strong aria-hidden="true">4.</strong> Understanding Ownership</a></li><li><ol class="section"><li class="expanded "><a href="ch04-01-what-is-ownership.html"><strong aria-hidden="true">4.1.</strong> What is Ownership?</a></li><li class="expanded "><a href="ch04-02-references-and-borrowing.html"><strong aria-hidden="true">4.2.</strong> References and Borrowing</a></li><li class="expanded "><a href="ch04-03-slices.html"><strong aria-hidden="true">4.3.</strong> The Slice Type</a></li></ol></li><li class="expanded "><a href="ch05-00-structs.html"><strong aria-hidden="true">5.</strong> Using Structs to Structure Related Data</a></li><li><ol class="section"><li class="expanded "><a href="ch05-01-defining-structs.html"><strong aria-hidden="true">5.1.</strong> Defining and Instantiating Structs</a></li><li class="expanded "><a href="ch05-02-example-structs.html"><strong aria-hidden="true">5.2.</strong> An Example Program Using Structs</a></li><li class="expanded "><a href="ch05-03-method-syntax.html"><strong aria-hidden="true">5.3.</strong> Method Syntax</a></li></ol></li><li class="expanded "><a href="ch06-00-enums.html"><strong aria-hidden="true">6.</strong> Enums and Pattern Matching</a></li><li><ol class="section"><li class="expanded "><a href="ch06-01-defining-an-enum.html"><strong aria-hidden="true">6.1.</strong> Defining an Enum</a></li><li class="expanded "><a href="ch06-02-match.html"><strong aria-hidden="true">6.2.</strong> The match Control Flow Operator</a></li><li class="expanded "><a href="ch06-03-if-let.html"><strong aria-hidden="true">6.3.</strong> Concise Control Flow with if let</a></li></ol></li><li class="expanded "><a href="ch07-00-managing-growing-projects-with-packages-crates-and-modules.html"><strong aria-hidden="true">7.</strong> Managing Growing Projects with Packages, Crates, and Modules</a></li><li><ol class="section"><li class="expanded "><a href="ch07-01-packages-and-crates.html"><strong aria-hidden="true">7.1.</strong> Packages and Crates</a></li><li class="expanded "><a href="ch07-02-defining-modules-to-control-scope-and-privacy.html"><strong aria-hidden="true">7.2.</strong> Defining Modules to Control Scope and Privacy</a></li><li class="expanded "><a href="ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html"><
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar" class="menu-bar">
<div id="menu-bar-sticky-container">
<div class="left-buttons">
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</button>
<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 (default)</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">The Rust Programming Language</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>
</div>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" name="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 type="text/javascript">
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><a class="header" href="#using-threads-to-run-code-simultaneously" id="using-threads-to-run-code-simultaneously">Using Threads to Run Code Simultaneously</a></h2>
<p>In most current operating systems, an executed programs code is run in a
<em>process</em>, and the operating system manages multiple processes at once. Within
your program, you can also have independent parts that run simultaneously. The
features that run these independent parts are called <em>threads</em>.</p>
<p>Splitting the computation in your program into multiple threads can improve
performance because the program does multiple tasks at the same time, but it
also adds complexity. Because threads can run simultaneously, theres no
inherent guarantee about the order in which parts of your code on different
threads will run. This can lead to problems, such as:</p>
<ul>
<li>Race conditions, where threads are accessing data or resources in an
inconsistent order</li>
<li>Deadlocks, where two threads are waiting for each other to finish using a
resource the other thread has, preventing both threads from continuing</li>
<li>Bugs that happen only in certain situations and are hard to reproduce and fix
reliably</li>
</ul>
<p>Rust attempts to mitigate the negative effects of using threads, but
programming in a multithreaded context still takes careful thought and requires
a code structure that is different from that in programs running in a single
thread.</p>
<p>Programming languages implement threads in a few different ways. Many operating
systems provide an API for creating new threads. This model where a language
calls the operating system APIs to create threads is sometimes called <em>1:1</em>,
meaning one operating system thread per one language thread.</p>
<p>Many programming languages provide their own special implementation of threads.
Programming language-provided threads are known as <em>green</em> threads, and
languages that use these green threads will execute them in the context of a
different number of operating system threads. For this reason, the
green-threaded model is called the <em>M:N</em> model: there are <code>M</code> green threads per
<code>N</code> operating system threads, where <code>M</code> and <code>N</code> are not necessarily the same
number.</p>
<p>Each model has its own advantages and trade-offs, and the trade-off most
important to Rust is runtime support. <em>Runtime</em> is a confusing term and can
have different meanings in different contexts.</p>
<p>In this context, by <em>runtime</em> we mean code that is included by the language in
every binary. This code can be large or small depending on the language, but
every non-assembly language will have some amount of runtime code. For that
reason, colloquially when people say a language has “no runtime,” they often
mean “small runtime.” Smaller runtimes have fewer features but have the
advantage of resulting in smaller binaries, which make it easier to combine the
language with other languages in more contexts. Although many languages are
okay with increasing the runtime size in exchange for more features, Rust needs
to have nearly no runtime and cannot compromise on being able to call into C to
maintain performance.</p>
<p>The green-threading M:N model requires a larger language runtime to manage
threads. As such, the Rust standard library only provides an implementation of
1:1 threading. Because Rust is such a low-level language, there are crates that
implement M:N threading if you would rather trade overhead for aspects such as
more control over which threads run when and lower costs of context switching,
for example.</p>
<p>Now that weve defined threads in Rust, lets explore how to use the
thread-related API provided by the standard library.</p>
<h3><a class="header" href="#creating-a-new-thread-with-spawn" id="creating-a-new-thread-with-spawn">Creating a New Thread with <code>spawn</code></a></h3>
<p>To create a new thread, we call the <code>thread::spawn</code> function and pass it a
closure (we talked about closures in Chapter 13) containing the code we want to
run in the new thread. The example in Listing 16-1 prints some text from a main
thread and other text from a new thread:</p>
<p><span class="filename">Filename: src/main.rs</span></p>
<pre><pre class="playpen"><code class="language-rust">use std::thread;
use std::time::Duration;
fn main() {
thread::spawn(|| {
for i in 1..10 {
println!(&quot;hi number {} from the spawned thread!&quot;, i);
thread::sleep(Duration::from_millis(1));
}
});
for i in 1..5 {
println!(&quot;hi number {} from the main thread!&quot;, i);
thread::sleep(Duration::from_millis(1));
}
}
</code></pre></pre>
<p><span class="caption">Listing 16-1: Creating a new thread to print one thing
while the main thread prints something else</span></p>
<p>Note that with this function, the new thread will be stopped when the main
thread ends, whether or not it has finished running. The output from this
program might be a little different every time, but it will look similar to the
following:</p>
<pre><code class="language-text">hi number 1 from the main thread!
hi number 1 from the spawned thread!
hi number 2 from the main thread!
hi number 2 from the spawned thread!
hi number 3 from the main thread!
hi number 3 from the spawned thread!
hi number 4 from the main thread!
hi number 4 from the spawned thread!
hi number 5 from the spawned thread!
</code></pre>
<p>The calls to <code>thread::sleep</code> force a thread to stop its execution for a short
duration, allowing a different thread to run. The threads will probably take
turns, but that isnt guaranteed: it depends on how your operating system
schedules the threads. In this run, the main thread printed first, even though
the print statement from the spawned thread appears first in the code. And even
though we told the spawned thread to print until <code>i</code> is 9, it only got to 5
before the main thread shut down.</p>
<p>If you run this code and only see output from the main thread, or dont see any
overlap, try increasing the numbers in the ranges to create more opportunities
for the operating system to switch between the threads.</p>
<h3><a class="header" href="#waiting-for-all-threads-to-finish-using-join-handles" id="waiting-for-all-threads-to-finish-using-join-handles">Waiting for All Threads to Finish Using <code>join</code> Handles</a></h3>
<p>The code in Listing 16-1 not only stops the spawned thread prematurely most of
the time due to the main thread ending, but also cant guarantee that the
spawned thread will get to run at all. The reason is that there is no guarantee
on the order in which threads run!</p>
<p>We can fix the problem of the spawned thread not getting to run, or not getting
to run completely, by saving the return value of <code>thread::spawn</code> in a variable.
The return type of <code>thread::spawn</code> is <code>JoinHandle</code>. A <code>JoinHandle</code> is an owned
value that, when we call the <code>join</code> method on it, will wait for its thread to
finish. Listing 16-2 shows how to use the <code>JoinHandle</code> of the thread we created
in Listing 16-1 and call <code>join</code> to make sure the spawned thread finishes before
<code>main</code> exits:</p>
<p><span class="filename">Filename: src/main.rs</span></p>
<pre><pre class="playpen"><code class="language-rust">use std::thread;
use std::time::Duration;
fn main() {
let handle = thread::spawn(|| {
for i in 1..10 {
println!(&quot;hi number {} from the spawned thread!&quot;, i);
thread::sleep(Duration::from_millis(1));
}
});
for i in 1..5 {
println!(&quot;hi number {} from the main thread!&quot;, i);
thread::sleep(Duration::from_millis(1));
}
handle.join().unwrap();
}
</code></pre></pre>
<p><span class="caption">Listing 16-2: Saving a <code>JoinHandle</code> from <code>thread::spawn</code>
to guarantee the thread is run to completion</span></p>
<p>Calling <code>join</code> on the handle blocks the thread currently running until the
thread represented by the handle terminates. <em>Blocking</em> a thread means that
thread is prevented from performing work or exiting. Because weve put the call
to <code>join</code> after the main threads <code>for</code> loop, running Listing 16-2 should
produce output similar to this:</p>
<pre><code class="language-text">hi number 1 from the main thread!
hi number 2 from the main thread!
hi number 1 from the spawned thread!
hi number 3 from the main thread!
hi number 2 from the spawned thread!
hi number 4 from the main thread!
hi number 3 from the spawned thread!
hi number 4 from the spawned thread!
hi number 5 from the spawned thread!
hi number 6 from the spawned thread!
hi number 7 from the spawned thread!
hi number 8 from the spawned thread!
hi number 9 from the spawned thread!
</code></pre>
<p>The two threads continue alternating, but the main thread waits because of the
call to <code>handle.join()</code> and does not end until the spawned thread is finished.</p>
<p>But lets see what happens when we instead move <code>handle.join()</code> before the
<code>for</code> loop in <code>main</code>, like this:</p>
<p><span class="filename">Filename: src/main.rs</span></p>
<pre><pre class="playpen"><code class="language-rust">use std::thread;
use std::time::Duration;
fn main() {
let handle = thread::spawn(|| {
for i in 1..10 {
println!(&quot;hi number {} from the spawned thread!&quot;, i);
thread::sleep(Duration::from_millis(1));
}
});
handle.join().unwrap();
for i in 1..5 {
println!(&quot;hi number {} from the main thread!&quot;, i);
thread::sleep(Duration::from_millis(1));
}
}
</code></pre></pre>
<p>The main thread will wait for the spawned thread to finish and then run its
<code>for</code> loop, so the output wont be interleaved anymore, as shown here:</p>
<pre><code class="language-text">hi number 1 from the spawned thread!
hi number 2 from the spawned thread!
hi number 3 from the spawned thread!
hi number 4 from the spawned thread!
hi number 5 from the spawned thread!
hi number 6 from the spawned thread!
hi number 7 from the spawned thread!
hi number 8 from the spawned thread!
hi number 9 from the spawned thread!
hi number 1 from the main thread!
hi number 2 from the main thread!
hi number 3 from the main thread!
hi number 4 from the main thread!
</code></pre>
<p>Small details, such as where <code>join</code> is called, can affect whether or not your
threads run at the same time.</p>
<h3><a class="header" href="#using-move-closures-with-threads" id="using-move-closures-with-threads">Using <code>move</code> Closures with Threads</a></h3>
<p>The <code>move</code> closure is often used alongside <code>thread::spawn</code> because it allows
you to use data from one thread in another thread.</p>
<p>In Chapter 13, we mentioned we can use the <code>move</code> keyword before the parameter
list of a closure to force the closure to take ownership of the values it uses
in the environment. This technique is especially useful when creating new
threads in order to transfer ownership of values from one thread to another.</p>
<p>Notice in Listing 16-1 that the closure we pass to <code>thread::spawn</code> takes no
arguments: were not using any data from the main thread in the spawned
threads code. To use data from the main thread in the spawned thread, the
spawned threads closure must capture the values it needs. Listing 16-3 shows
an attempt to create a vector in the main thread and use it in the spawned
thread. However, this wont yet work, as youll see in a moment.</p>
<p><span class="filename">Filename: src/main.rs</span></p>
<pre><code class="language-rust ignore does_not_compile">use std::thread;
fn main() {
let v = vec![1, 2, 3];
let handle = thread::spawn(|| {
println!(&quot;Here's a vector: {:?}&quot;, v);
});
handle.join().unwrap();
}
</code></pre>
<p><span class="caption">Listing 16-3: Attempting to use a vector created by the
main thread in another thread</span></p>
<p>The closure uses <code>v</code>, so it will capture <code>v</code> and make it part of the closures
environment. Because <code>thread::spawn</code> runs this closure in a new thread, we
should be able to access <code>v</code> inside that new thread. But when we compile this
example, we get the following error:</p>
<pre><code class="language-text">error[E0373]: closure may outlive the current function, but it borrows `v`,
which is owned by the current function
--&gt; src/main.rs:6:32
|
6 | let handle = thread::spawn(|| {
| ^^ may outlive borrowed value `v`
7 | println!(&quot;Here's a vector: {:?}&quot;, v);
| - `v` is borrowed here
|
help: to force the closure to take ownership of `v` (and any other referenced
variables), use the `move` keyword
|
6 | let handle = thread::spawn(move || {
| ^^^^^^^
</code></pre>
<p>Rust <em>infers</em> how to capture <code>v</code>, and because <code>println!</code> only needs a reference
to <code>v</code>, the closure tries to borrow <code>v</code>. However, theres a problem: Rust cant
tell how long the spawned thread will run, so it doesnt know if the reference
to <code>v</code> will always be valid.</p>
<p>Listing 16-4 provides a scenario thats more likely to have a reference to <code>v</code>
that wont be valid:</p>
<p><span class="filename">Filename: src/main.rs</span></p>
<pre><code class="language-rust ignore does_not_compile">use std::thread;
fn main() {
let v = vec![1, 2, 3];
let handle = thread::spawn(|| {
println!(&quot;Here's a vector: {:?}&quot;, v);
});
drop(v); // oh no!
handle.join().unwrap();
}
</code></pre>
<p><span class="caption">Listing 16-4: A thread with a closure that attempts to
capture a reference to <code>v</code> from a main thread that drops <code>v</code></span></p>
<p>If we were allowed to run this code, theres a possibility the spawned thread
would be immediately put in the background without running at all. The spawned
thread has a reference to <code>v</code> inside, but the main thread immediately drops
<code>v</code>, using the <code>drop</code> function we discussed in Chapter 15. Then, when the
spawned thread starts to execute, <code>v</code> is no longer valid, so a reference to it
is also invalid. Oh no!</p>
<p>To fix the compiler error in Listing 16-3, we can use the error messages
advice:</p>
<pre><code class="language-text">help: to force the closure to take ownership of `v` (and any other referenced
variables), use the `move` keyword
|
6 | let handle = thread::spawn(move || {
| ^^^^^^^
</code></pre>
<p>By adding the <code>move</code> keyword before the closure, we force the closure to take
ownership of the values its using rather than allowing Rust to infer that it
should borrow the values. The modification to Listing 16-3 shown in Listing
16-5 will compile and run as we intend:</p>
<p><span class="filename">Filename: src/main.rs</span></p>
<pre><pre class="playpen"><code class="language-rust">use std::thread;
fn main() {
let v = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!(&quot;Here's a vector: {:?}&quot;, v);
});
handle.join().unwrap();
}
</code></pre></pre>
<p><span class="caption">Listing 16-5: Using the <code>move</code> keyword to force a closure
to take ownership of the values it uses</span></p>
<p>What would happen to the code in Listing 16-4 where the main thread called
<code>drop</code> if we use a <code>move</code> closure? Would <code>move</code> fix that case? Unfortunately,
no; we would get a different error because what Listing 16-4 is trying to do
isnt allowed for a different reason. If we added <code>move</code> to the closure, we
would move <code>v</code> into the closures environment, and we could no longer call
<code>drop</code> on it in the main thread. We would get this compiler error instead:</p>
<pre><code class="language-text">error[E0382]: use of moved value: `v`
--&gt; src/main.rs:10:10
|
6 | let handle = thread::spawn(move || {
| ------- value moved (into closure) here
...
10 | drop(v); // oh no!
| ^ value used here after move
|
= note: move occurs because `v` has type `std::vec::Vec&lt;i32&gt;`, which does
not implement the `Copy` trait
</code></pre>
<p>Rusts ownership rules have saved us again! We got an error from the code in
Listing 16-3 because Rust was being conservative and only borrowing <code>v</code> for the
thread, which meant the main thread could theoretically invalidate the spawned
threads reference. By telling Rust to move ownership of <code>v</code> to the spawned
thread, were guaranteeing Rust that the main thread wont use <code>v</code> anymore. If
we change Listing 16-4 in the same way, were then violating the ownership
rules when we try to use <code>v</code> in the main thread. The <code>move</code> keyword overrides
Rusts conservative default of borrowing; it doesnt let us violate the
ownership rules.</p>
<p>With a basic understanding of threads and the thread API, lets look at what we
can <em>do</em> with threads.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="ch16-00-concurrency.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" href="ch16-02-message-passing.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 href="ch16-00-concurrency.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a href="ch16-02-message-passing.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 type="text/javascript">
window.playpen_copyable = true;
</script>
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
<script src="book.js" type="text/javascript" charset="utf-8"></script>
<!-- Custom JS scripts -->
<script type="text/javascript" src="ferris.js"></script>
</body>
</html>