Files
docs-rust/ch16/ch16-01-threads.html
2026-06-22 21:27:36 +05:30

356 lines
18 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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 programs 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, 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, 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. (Rusts 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 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 <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 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>
<!-- 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 cant 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&lt;T&gt;</code>. A <code>JoinHandle&lt;T&gt;</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&lt;T&gt;</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&lt;T&gt;</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 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>
<!-- 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 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>
<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 wont 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>Well 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 well
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: 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 work yet, as youll 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 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-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
--&gt; 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`
--&gt; 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, theres a problem: Rust cant
tell how long the spawned thread will run, so it doesnt know whether 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>
<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, theres 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 messages
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 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>
<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
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-console">$ cargo run
Compiling threads v0.1.0 (file:///projects/threads)
error[E0382]: use of moved value: `v`
--&gt; src/main.rs:10:10
|
4 | let v = vec![1, 2, 3];
| - move occurs because `v` has type `Vec&lt;i32&gt;`, 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>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 to 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>Now that weve covered what threads are and the methods supplied by the thread
API, lets look at some situations in which we can use threads.</p>
</body>
</html>