356 lines
18 KiB
HTML
356 lines
18 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>Using Threads to Run Code Simultaneously</title>
|
||
</head>
|
||
<body>
|
||
<h2 id="using-threads-to-run-code-simultaneously"><a class="header" href="#using-threads-to-run-code-simultaneously">Using Threads to Run Code Simultaneously</a></h2>
|
||
<p>In most current operating systems, an executed program’s code is run in a
|
||
<em>process</em>, and the operating system will manage multiple processes at once.
|
||
Within a program, you can also have independent parts that run simultaneously.
|
||
The features that run these independent parts are called <em>threads</em>. For
|
||
example, a web server could have multiple threads so that it can respond to
|
||
more than one request at the same time.</p>
|
||
<p>Splitting the computation in your program into multiple threads to run multiple
|
||
tasks at the same time can improve performance, but it also adds complexity.
|
||
Because threads can run simultaneously, there’s 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, in which threads are accessing data or resources in an
|
||
inconsistent order</li>
|
||
<li>Deadlocks, in which two threads are waiting for each other, preventing both
|
||
threads from continuing</li>
|
||
<li>Bugs that only happen 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, and many
|
||
operating systems provide an API the programming language can call for creating
|
||
new threads. The Rust standard library uses a <em>1:1</em> model of thread
|
||
implementation, whereby a program uses one operating system thread per one
|
||
language thread. There are crates that implement other models of threading that
|
||
make different trade-offs to the 1:1 model. (Rust’s async system, which we will
|
||
see in the next chapter, provides another approach to concurrency as well.)</p>
|
||
<h3 id="creating-a-new-thread-with-spawn"><a class="header" href="#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>
|
||
<figure class="listing" id="listing-16-1">
|
||
<span class="file-name">Filename: src/main.rs</span>
|
||
<pre class="playground"><code class="language-rust edition2024">use std::thread;
|
||
use std::time::Duration;
|
||
|
||
fn main() {
|
||
thread::spawn(|| {
|
||
for i in 1..10 {
|
||
println!("hi number {i} from the spawned thread!");
|
||
thread::sleep(Duration::from_millis(1));
|
||
}
|
||
});
|
||
|
||
for i in 1..5 {
|
||
println!("hi number {i} from the main thread!");
|
||
thread::sleep(Duration::from_millis(1));
|
||
}
|
||
}</code></pre>
|
||
<figcaption><a href="#listing-16-1">Listing 16-1</a>: Creating a new thread to print one thing while the main thread prints something else</figcaption>
|
||
</figure>
|
||
<p>Note that when the main thread of a Rust program completes, all spawned threads
|
||
are shut down, whether or not they have finished running. The output from this
|
||
program might be a little different every time, but it will look similar to the
|
||
following:</p>
|
||
<!-- Not extracting output because changes to this output aren't significant;
|
||
the changes are likely to be due to the threads running differently rather than
|
||
changes in the compiler -->
|
||
<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 isn’t 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 <code>9</code>, it only got to <code>5</code>
|
||
before the main thread shut down.</p>
|
||
<p>If you run this code and only see output from the main thread, or don’t see any
|
||
overlap, try increasing the numbers in the ranges to create more opportunities
|
||
for the operating system to switch between the threads.</p>
|
||
<!-- Old headings. Do not remove or links may break. -->
|
||
<p><a id="waiting-for-all-threads-to-finish-using-join-handles"></a></p>
|
||
<h3 id="waiting-for-all-threads-to-finish"><a class="header" href="#waiting-for-all-threads-to-finish">Waiting for All Threads to Finish</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 because there is no guarantee on
|
||
the order in which threads run, we also can’t guarantee that the spawned thread
|
||
will get to run at all!</p>
|
||
<p>We can fix the problem of the spawned thread not running or of it ending
|
||
prematurely by saving the return value of <code>thread::spawn</code> in a variable. The
|
||
return type of <code>thread::spawn</code> is <code>JoinHandle<T></code>. A <code>JoinHandle<T></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<T></code> of the
|
||
thread we created in Listing 16-1 and how to call <code>join</code> to make sure the
|
||
spawned thread finishes before <code>main</code> exits.</p>
|
||
<figure class="listing" id="listing-16-2">
|
||
<span class="file-name">Filename: src/main.rs</span>
|
||
<pre class="playground"><code class="language-rust edition2024">use std::thread;
|
||
use std::time::Duration;
|
||
|
||
fn main() {
|
||
let handle = thread::spawn(|| {
|
||
for i in 1..10 {
|
||
println!("hi number {i} from the spawned thread!");
|
||
thread::sleep(Duration::from_millis(1));
|
||
}
|
||
});
|
||
|
||
for i in 1..5 {
|
||
println!("hi number {i} from the main thread!");
|
||
thread::sleep(Duration::from_millis(1));
|
||
}
|
||
|
||
handle.join().unwrap();
|
||
}</code></pre>
|
||
<figcaption><a href="#listing-16-2">Listing 16-2</a>: Saving a <code>JoinHandle<T></code> from <code>thread::spawn</code> to guarantee the thread is run to completion</figcaption>
|
||
</figure>
|
||
<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 we’ve put the call
|
||
to <code>join</code> after the main thread’s <code>for</code> loop, running Listing 16-2 should
|
||
produce output similar to this:</p>
|
||
<!-- Not extracting output because changes to this output aren't significant;
|
||
the changes are likely to be due to the threads running differently rather than
|
||
changes in the compiler -->
|
||
<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 let’s see what happens when we instead move <code>handle.join()</code> before the
|
||
<code>for</code> loop in <code>main</code>, like this:</p>
|
||
<figure class="listing">
|
||
<span class="file-name">Filename: src/main.rs</span>
|
||
<pre class="playground"><code class="language-rust edition2024">use std::thread;
|
||
use std::time::Duration;
|
||
|
||
fn main() {
|
||
let handle = thread::spawn(|| {
|
||
for i in 1..10 {
|
||
println!("hi number {i} from the spawned thread!");
|
||
thread::sleep(Duration::from_millis(1));
|
||
}
|
||
});
|
||
|
||
handle.join().unwrap();
|
||
|
||
for i in 1..5 {
|
||
println!("hi number {i} from the main thread!");
|
||
thread::sleep(Duration::from_millis(1));
|
||
}
|
||
}</code></pre>
|
||
</figure>
|
||
<p>The main thread will wait for the spawned thread to finish and then run its
|
||
<code>for</code> loop, so the output won’t be interleaved anymore, as shown here:</p>
|
||
<!-- Not extracting output because changes to this output aren't significant;
|
||
the changes are likely to be due to the threads running differently rather than
|
||
changes in the compiler -->
|
||
<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 id="using-move-closures-with-threads"><a class="header" href="#using-move-closures-with-threads">Using <code>move</code> Closures with Threads</a></h3>
|
||
<p>We’ll often use the <code>move</code> keyword with closures passed to <code>thread::spawn</code>
|
||
because the closure will then take ownership of the values it uses from the
|
||
environment, thus transferring ownership of those values from one thread to
|
||
another. In <a href="../ch13/ch13-01-closures.html#capturing-references-or-moving-ownership">“Capturing References or Moving Ownership”</a><!-- ignore
|
||
--> in Chapter 13, we discussed <code>move</code> in the context of closures. Now we’ll
|
||
concentrate more on the interaction between <code>move</code> and <code>thread::spawn</code>.</p>
|
||
<p>Notice in Listing 16-1 that the closure we pass to <code>thread::spawn</code> takes no
|
||
arguments: We’re not using any data from the main thread in the spawned
|
||
thread’s code. To use data from the main thread in the spawned thread, the
|
||
spawned thread’s 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 won’t work yet, as you’ll see in a moment.</p>
|
||
<figure class="listing" id="listing-16-3">
|
||
<span class="file-name">Filename: src/main.rs</span>
|
||
<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!("Here's a vector: {v:?}");
|
||
});
|
||
|
||
handle.join().unwrap();
|
||
}</code></pre>
|
||
<figcaption><a href="#listing-16-3">Listing 16-3</a>: Attempting to use a vector created by the main thread in another thread</figcaption>
|
||
</figure>
|
||
<p>The closure uses <code>v</code>, so it will capture <code>v</code> and make it part of the closure’s
|
||
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-console">$ cargo run
|
||
Compiling threads v0.1.0 (file:///projects/threads)
|
||
error[E0373]: closure may outlive the current function, but it borrows `v`, which is owned by the current function
|
||
--> src/main.rs:6:32
|
||
|
|
||
6 | let handle = thread::spawn(|| {
|
||
| ^^ may outlive borrowed value `v`
|
||
7 | println!("Here's a vector: {v:?}");
|
||
| - `v` is borrowed here
|
||
|
|
||
note: function requires argument type to outlive `'static`
|
||
--> src/main.rs:6:18
|
||
|
|
||
6 | let handle = thread::spawn(|| {
|
||
| __________________^
|
||
7 | | println!("Here's a vector: {v:?}");
|
||
8 | | });
|
||
| |______^
|
||
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 || {
|
||
| ++++
|
||
|
||
For more information about this error, try `rustc --explain E0373`.
|
||
error: could not compile `threads` (bin "threads") due to 1 previous error
|
||
</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, there’s a problem: Rust can’t
|
||
tell how long the spawned thread will run, so it doesn’t know whether the
|
||
reference to <code>v</code> will always be valid.</p>
|
||
<p>Listing 16-4 provides a scenario that’s more likely to have a reference to <code>v</code>
|
||
that won’t be valid.</p>
|
||
<figure class="listing" id="listing-16-4">
|
||
<span class="file-name">Filename: src/main.rs</span>
|
||
<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!("Here's a vector: {v:?}");
|
||
});
|
||
|
||
drop(v); // oh no!
|
||
|
||
handle.join().unwrap();
|
||
}</code></pre>
|
||
<figcaption><a href="#listing-16-4">Listing 16-4</a>: A thread with a closure that attempts to capture a reference to <code>v</code> from a main thread that drops <code>v</code></figcaption>
|
||
</figure>
|
||
<p>If Rust allowed us to run this code, there’s a possibility that 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 message’s
|
||
advice:</p>
|
||
<!-- manual-regeneration
|
||
after automatic regeneration, look at listings/ch16-fearless-concurrency/listing-16-03/output.txt and copy the relevant part
|
||
-->
|
||
<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 it’s 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>
|
||
<figure class="listing" id="listing-16-5">
|
||
<span class="file-name">Filename: src/main.rs</span>
|
||
<pre class="playground"><code class="language-rust edition2024">use std::thread;
|
||
|
||
fn main() {
|
||
let v = vec![1, 2, 3];
|
||
|
||
let handle = thread::spawn(move || {
|
||
println!("Here's a vector: {v:?}");
|
||
});
|
||
|
||
handle.join().unwrap();
|
||
}</code></pre>
|
||
<figcaption><a href="#listing-16-5">Listing 16-5</a>: Using the <code>move</code> keyword to force a closure to take ownership of the values it uses</figcaption>
|
||
</figure>
|
||
<p>We might be tempted to try the same thing to fix the code in Listing 16-4 where
|
||
the main thread called <code>drop</code> by using a <code>move</code> closure. However, this fix will
|
||
not work because what Listing 16-4 is trying to do is disallowed for a
|
||
different reason. If we added <code>move</code> to the closure, we would move <code>v</code> into the
|
||
closure’s 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-console">$ cargo run
|
||
Compiling threads v0.1.0 (file:///projects/threads)
|
||
error[E0382]: use of moved value: `v`
|
||
--> src/main.rs:10:10
|
||
|
|
||
4 | let v = vec![1, 2, 3];
|
||
| - move occurs because `v` has type `Vec<i32>`, which does not implement the `Copy` trait
|
||
5 |
|
||
6 | let handle = thread::spawn(move || {
|
||
| ------- value moved into closure here
|
||
7 | println!("Here's a vector: {v:?}");
|
||
| - variable moved due to use in closure
|
||
...
|
||
10 | drop(v); // oh no!
|
||
| ^ value used here after move
|
||
|
|
||
help: consider cloning the value before moving it into the closure
|
||
|
|
||
6 ~ let value = v.clone();
|
||
7 ~ let handle = thread::spawn(move || {
|
||
8 ~ println!("Here's a vector: {value:?}");
|
||
|
|
||
|
||
For more information about this error, try `rustc --explain E0382`.
|
||
error: could not compile `threads` (bin "threads") due to 1 previous error
|
||
</code></pre>
|
||
<p>Rust’s 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
|
||
thread’s reference. By telling Rust to move ownership of <code>v</code> to the spawned
|
||
thread, we’re guaranteeing to Rust that the main thread won’t use <code>v</code> anymore.
|
||
If we change Listing 16-4 in the same way, we’re then violating the ownership
|
||
rules when we try to use <code>v</code> in the main thread. The <code>move</code> keyword overrides
|
||
Rust’s conservative default of borrowing; it doesn’t let us violate the
|
||
ownership rules.</p>
|
||
<p>Now that we’ve covered what threads are and the methods supplied by the thread
|
||
API, let’s look at some situations in which we can use threads.</p>
|
||
</body>
|
||
</html>
|