feat: added cleanscript
This commit is contained in:
112
ch17/ch17-06-futures-tasks-threads.html
Normal file
112
ch17/ch17-06-futures-tasks-threads.html
Normal file
@@ -0,0 +1,112 @@
|
||||
<!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. We’ve seen another approach in this chapter: using async with
|
||||
futures and streams. If you’re wondering when to choose one method over the other,
|
||||
the answer is: it depends! And in many cases, the choice isn’t 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 don’t have an OS at all, so they also
|
||||
don’t have threads.</p>
|
||||
<p>The async model provides a different—and ultimately complementary—set of
|
||||
tradeoffs. In the async model, concurrent operations don’t 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, it’s
|
||||
managed by library-level code: the runtime.</p>
|
||||
<p>There’s 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 Rust’s 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 doesn’t 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 we’ve 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 system’s 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 don’t have to choose
|
||||
between threads and async. You can use them together freely, letting each
|
||||
play the part it’s 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 isn’t the last you’ll 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, we’ll talk about idiomatic ways to model problems and structure solutions
|
||||
as your Rust programs get bigger. In addition, we’ll discuss how Rust’s idioms
|
||||
relate to those you might be familiar with from object-oriented programming.</p>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user