Files
docs-rust/ch17/ch17-06-futures-tasks-threads.html
2026-06-22 21:27:36 +05:30

113 lines
6.7 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>Futures, Tasks, and Threads</title>
</head>
<body>
<h2 id="putting-it-all-together-futures-tasks-and-threads"><a class="header" href="#putting-it-all-together-futures-tasks-and-threads">Putting It All Together: Futures, Tasks, and Threads</a></h2>
<p>As we saw in <a href="http://localhost:3000/ch16-00-concurrency.html">Chapter 16</a><!-- ignore -->, threads provide one approach to
concurrency. Weve seen another approach in this chapter: using async with
futures and streams. If youre wondering when to choose one method over the other,
the answer is: it depends! And in many cases, the choice isnt threads <em>or</em>
async but rather threads <em>and</em> async.</p>
<p>Many operating systems have supplied threading-based concurrency models for
decades now, and many programming languages support them as a result. However,
these models are not without their tradeoffs. On many operating systems, they
use a fair bit of memory for each thread. Threads are also only an option when
your operating system and hardware support them. Unlike mainstream desktop and
mobile computers, some embedded systems dont have an OS at all, so they also
dont have threads.</p>
<p>The async model provides a different—and ultimately complementary—set of
tradeoffs. In the async model, concurrent operations dont require their own
threads. Instead, they can run on tasks, as when we used <code>trpl::spawn_task</code> to
kick off work from a synchronous function in the streams section. A task is
similar to a thread, but instead of being managed by the operating system, its
managed by library-level code: the runtime.</p>
<p>Theres a reason the APIs for spawning threads and spawning tasks are so
similar. Threads act as a boundary for sets of synchronous operations;
concurrency is possible <em>between</em> threads. Tasks act as a boundary for sets of
<em>asynchronous</em> operations; concurrency is possible both <em>between</em> and <em>within</em>
tasks, because a task can switch between futures in its body. Finally, futures
are Rusts most granular unit of concurrency, and each future may represent a
tree of other futures. The runtime—specifically, its executor—manages tasks,
and tasks manage futures. In that regard, tasks are similar to lightweight,
runtime-managed threads with added capabilities that come from being managed by
a runtime instead of by the operating system.</p>
<p>This doesnt mean that async tasks are always better than threads (or vice
versa). Concurrency with threads is in some ways a simpler programming model
than concurrency with <code>async</code>. That can be a strength or a weakness. Threads are
somewhat “fire and forget”; they have no native equivalent to a future, so they
simply run to completion without being interrupted except by the operating
system itself.</p>
<p>And it turns out that threads and tasks often work
very well together, because tasks can (at least in some runtimes) be moved
around between threads. In fact, under the hood, the runtime weve been
using—including the <code>spawn_blocking</code> and <code>spawn_task</code> functions—is multithreaded
by default! Many runtimes use an approach called <em>work stealing</em> to
transparently move tasks around between threads, based on how the threads are
currently being utilized, to improve the systems overall performance. That
approach actually requires threads <em>and</em> tasks, and therefore futures.</p>
<p>When thinking about which method to use when, consider these rules of thumb:</p>
<ul>
<li>If the work is <em>very parallelizable</em> (that is, CPU-bound), such as processing
a bunch of data where each part can be processed separately, threads are a
better choice.</li>
<li>If the work is <em>very concurrent</em> (that is, I/O-bound), such as handling
messages from a bunch of different sources that may come in at different
intervals or different rates, async is a better choice.</li>
</ul>
<p>And if you need both parallelism and concurrency, you dont have to choose
between threads and async. You can use them together freely, letting each
play the part its best at. For example, Listing 17-25 shows a fairly common
example of this kind of mix in real-world Rust code.</p>
<figure class="listing" id="listing-17-25">
<span class="file-name">Filename: src/main.rs</span>
<pre class="playground"><code class="language-rust edition2024"><span class="boring">extern crate trpl; // for mdbook test
</span><span class="boring">
</span>use std::{thread, time::Duration};
fn main() {
let (tx, mut rx) = trpl::channel();
thread::spawn(move || {
for i in 1..11 {
tx.send(i).unwrap();
thread::sleep(Duration::from_secs(1));
}
});
trpl::block_on(async {
while let Some(message) = rx.recv().await {
println!("{message}");
}
});
}</code></pre>
<figcaption><a href="#listing-17-25">Listing 17-25</a>: Sending messages with blocking code in a thread and awaiting the messages in an async block</figcaption>
</figure>
<p>We begin by creating an async channel, then spawning a thread that takes
ownership of the sender side of the channel using the <code>move</code> keyword. Within
the thread, we send the numbers 1 through 10, sleeping for a second between
each. Finally, we run a future created with an async block passed to
<code>trpl::block_on</code> just as we have throughout the chapter. In that future, we
await those messages, just as in the other message-passing examples we have
seen.</p>
<p>To return to the scenario we opened the chapter with, imagine running a set of
video encoding tasks using a dedicated thread (because video encoding is
compute-bound) but notifying the UI that those operations are done with an
async channel. There are countless examples of these kinds of combinations in
real-world use cases.</p>
<h2 id="summary"><a class="header" href="#summary">Summary</a></h2>
<p>This isnt the last youll see of concurrency in this book. The project in
<a href="../ch21/ch21-00-final-project-a-web-server.html">Chapter 21</a><!-- ignore --> will apply these concepts in a more realistic
situation than the simpler examples discussed here and compare problem-solving
with threading versus tasks and futures more directly.</p>
<p>No matter which of these approaches you choose, Rust gives you the tools you
need to write safe, fast, concurrent code—whether for a high-throughput web
server or an embedded operating system.</p>
<p>Next, well talk about idiomatic ways to model problems and structure solutions
as your Rust programs get bigger. In addition, well discuss how Rusts idioms
relate to those you might be familiar with from object-oriented programming.</p>
</body>
</html>