]> git.proxmox.com Git - rustc.git/blobdiff - src/doc/book/src/ch20-02-multithreaded.md
New upstream version 1.63.0+dfsg1
[rustc.git] / src / doc / book / src / ch20-02-multithreaded.md
index af52f057ed3be0ecf02ca785c0d697cfc2b225f0..bd1dc25ab9affecefb340e3e1375343acfeff6e0 100644 (file)
@@ -21,14 +21,18 @@ for 5 seconds before responding.
 {{#rustdoc_include ../listings/ch20-web-server/listing-20-10/src/main.rs:here}}
 ```
 
-<span class="caption">Listing 20-10: Simulating a slow request by recognizing
-*/sleep* and sleeping for 5 seconds</span>
+<span class="caption">Listing 20-10: Simulating a slow request by sleeping for
+5 seconds</span>
 
-This code is a bit messy, but it’s good enough for simulation purposes. We
-created a second request `sleep`, whose data our server recognizes. We added an
-`else if` after the `if` block to check for the request to */sleep*. When that
-request is received, the server will sleep for 5 seconds before rendering the
-successful HTML page.
+We switched from `if` to `match` now that we have three cases. We need to
+explicitly match on a slice of `request_line` to pattern match against the
+string literal values; `match` doesn’t do automatic referencing and
+dereferencing like the equality method does.
+
+The first arm is the same as the `if` block from Listing 20-9. The second arm
+matches a request to */sleep*. When that request is received, the server will
+sleep for 5 seconds before rendering the successful HTML page. The third arm is
+the same as the `else` block from Listing 20-9.
 
 You can see how primitive our server is: real libraries would handle the
 recognition of multiple requests in a much less verbose way!
@@ -39,9 +43,8 @@ you enter the */* URI a few times, as before, you’ll see it respond quickly.
 But if you enter */sleep* and then load */*, you’ll see that */* waits until
 `sleep` has slept for its full 5 seconds before loading.
 
-There are multiple ways we could change how our web server works to avoid
-having more requests back up behind a slow request; the one we’ll implement is
-a thread pool.
+There are multiple techniques we could use to avoid requests backing up behind
+a slow request; the one we’ll implement is a thread pool.
 
 ### Improving Throughput with a Thread Pool
 
@@ -60,21 +63,22 @@ for each request as it came in, someone making 10 million requests to our
 server could create havoc by using up all our server’s resources and grinding
 the processing of requests to a halt.
 
-Rather than spawning unlimited threads, we’ll have a fixed number of threads
-waiting in the pool. As requests come in, they’ll be sent to the pool for
+Rather than spawning unlimited threads, then, we’ll have a fixed number of
+threads waiting in the pool. Requests that come in are sent to the pool for
 processing. The pool will maintain a queue of incoming requests. Each of the
 threads in the pool will pop off a request from this queue, handle the request,
-and then ask the queue for another request. With this design, we can process
-`N` requests concurrently, where `N` is the number of threads. If each thread
-is responding to a long-running request, subsequent requests can still back up
-in the queue, but we’ve increased the number of long-running requests we can
-handle before reaching that point.
+and then ask the queue for another request. With this design, we can process up
+to `N` requests concurrently, where `N` is the number of threads. If each
+thread is responding to a long-running request, subsequent requests can still
+back up in the queue, but we’ve increased the number of long-running requests
+we can handle before reaching that point.
 
 This technique is just one of many ways to improve the throughput of a web
-server. Other options you might explore are the fork/join model and the
-single-threaded async I/O model. If you’re interested in this topic, you can
-read more about other solutions and try to implement them in Rust; with a
-low-level language like Rust, all of these options are possible.
+server. Other options you might explore are the *fork/join model*, the
+*single-threaded async I/O model*, or the *multi-threaded async I/O model*. If
+you’re interested in this topic, you can read more about other solutions and
+try to implement them; with a low-level language like Rust, all of these
+options are possible.
 
 Before we begin implementing a thread pool, let’s talk about what using the
 pool should look like. When you’re trying to design code, writing the client
@@ -86,15 +90,21 @@ designing the public API.
 Similar to how we used test-driven development in the project in Chapter 12,
 we’ll use compiler-driven development here. We’ll write the code that calls the
 functions we want, and then we’ll look at errors from the compiler to determine
-what we should change next to get the code to work.
+what we should change next to get the code to work. Before we do that, however,
+we’ll explore the technique we’re not going to use as a starting point.
+
+<!-- Old headings. Do not remove or links may break. -->
+<a id="code-structure-if-we-could-spawn-a-thread-for-each-request"></a>
 
-#### Code Structure If We Could Spawn a Thread for Each Request
+#### Spawning a Thread for Each Request
 
 First, let’s explore how our code might look if it did create a new thread for
 every connection. As mentioned earlier, this isn’t our final plan due to the
 problems with potentially spawning an unlimited number of threads, but it is a
-starting point. Listing 20-11 shows the changes to make to `main` to spawn a
-new thread to handle each stream within the `for` loop.
+starting point to get a working multithreaded server first. Then we’ll add the
+thread pool as an improvement, and contrasting the two solutions will be
+easier. Listing 20-11 shows the changes to make to `main` to spawn a new thread
+to handle each stream within the `for` loop.
 
 <span class="filename">Filename: src/main.rs</span>
 
@@ -108,11 +118,14 @@ stream</span>
 As you learned in Chapter 16, `thread::spawn` will create a new thread and then
 run the code in the closure in the new thread. If you run this code and load
 */sleep* in your browser, then */* in two more browser tabs, you’ll indeed see
-that the requests to */* don’t have to wait for */sleep* to finish. But as we
-mentioned, this will eventually overwhelm the system because you’d be making
+that the requests to */* don’t have to wait for */sleep* to finish. However, as
+we mentioned, this will eventually overwhelm the system because you’d be making
 new threads without any limit.
 
-#### Creating a Similar Interface for a Finite Number of Threads
+<!-- Old headings. Do not remove or links may break. -->
+<a id="creating-a-similar-interface-for-a-finite-number-of-threads"></a>
+
+#### Creating a Finite Number of Threads
 
 We want our thread pool to work in a similar, familiar way so switching from
 threads to a thread pool doesn’t require large changes to the code that uses
@@ -134,7 +147,10 @@ run for each stream. We need to implement `pool.execute` so it takes the
 closure and gives it to a thread in the pool to run. This code won’t yet
 compile, but we’ll try so the compiler can guide us in how to fix it.
 
-#### Building the `ThreadPool` Struct Using Compiler Driven Development
+<!-- Old headings. Do not remove or links may break. -->
+<a id="building-the-threadpool-struct-using-compiler-driven-development"></a>
+
+#### Building `ThreadPool` Using Compiler Driven Development
 
 Make the changes in Listing 20-12 to *src/main.rs*, and then let’s use the
 compiler errors from `cargo check` to drive our development. Here is the first
@@ -161,17 +177,13 @@ definition of a `ThreadPool` struct that we can have for now:
 {{#rustdoc_include ../listings/ch20-web-server/no-listing-01-define-threadpool-struct/src/lib.rs}}
 ```
 
-Then create a new directory, *src/bin*, and move the binary crate rooted in
-*src/main.rs* into *src/bin/main.rs*. Doing so will make the library crate the
-primary crate in the *hello* directory; we can still run the binary in
-*src/bin/main.rs* using `cargo run`. After moving the *main.rs* file, edit it
-to bring the library crate in and bring `ThreadPool` into scope by adding the
-following code to the top of *src/bin/main.rs*:
+Then edit *main.rs* file to bring `ThreadPool` into scope from the library
+crate by adding the following code to the top of *src/main.rs*:
 
-<span class="filename">Filename: src/bin/main.rs</span>
+<span class="filename">Filename: src/main.rs</span>
 
 ```rust,ignore
-{{#rustdoc_include ../listings/ch20-web-server/no-listing-01-define-threadpool-struct/src/bin/main.rs:here}}
+{{#rustdoc_include ../listings/ch20-web-server/no-listing-01-define-threadpool-struct/src/main.rs:here}}
 ```
 
 This code still won’t work, but let’s check it again to get the next error that
@@ -206,12 +218,11 @@ Let’s check the code again:
 ```
 
 Now the error occurs because we don’t have an `execute` method on `ThreadPool`.
-Recall from the [“Creating a Similar Interface for a Finite Number of
-Threads”](#creating-a-similar-interface-for-a-finite-number-of-threads)<!--
-ignore --> section that we decided our thread pool should have an interface
-similar to `thread::spawn`. In addition, we’ll implement the `execute` function
-so it takes the closure it’s given and gives it to an idle thread in the pool
-to run.
+Recall from the [“Creating a Finite Number of
+Threads”](#creating-a-finite-number-of-threads)<!-- ignore --> section that we
+decided our thread pool should have an interface similar to `thread::spawn`. In
+addition, we’ll implement the `execute` function so it takes the closure it’s
+given and gives it to an idle thread in the pool to run.
 
 We’ll define the `execute` method on `ThreadPool` to take a closure as a
 parameter. Recall from the [“Moving Captured Values Out of the Closure and the
@@ -294,29 +305,29 @@ zero by using the `assert!` macro, as shown in Listing 20-13.
 <span class="caption">Listing 20-13: Implementing `ThreadPool::new` to panic if
 `size` is zero</span>
 
-We’ve added some documentation for our `ThreadPool` with doc comments. Note
-that we followed good documentation practices by adding a section that calls
-out the situations in which our function can panic, as discussed in Chapter 14.
-Try running `cargo doc --open` and clicking the `ThreadPool` struct to see what
-the generated docs for `new` look like!
+We’ve also added some documentation for our `ThreadPool` with doc comments.
+Note that we followed good documentation practices by adding a section that
+calls out the situations in which our function can panic, as discussed in
+Chapter 14. Try running `cargo doc --open` and clicking the `ThreadPool` struct
+to see what the generated docs for `new` look like!
 
-Instead of adding the `assert!` macro as we’ve done here, we could make `new`
-return a `Result` like we did with `Config::new` in the I/O project in Listing
-12-9. But we’ve decided in this case that trying to create a thread pool
-without any threads should be an unrecoverable error. If you’re feeling
-ambitious, try to write a version of `new` with the following signature to
-compare both versions:
+Instead of adding the `assert!` macro as we’ve done here, we could change `new`
+into `build` and return a `Result` like we did with `Config::build` in the I/O
+project in Listing 12-9. But we’ve decided in this case that trying to create a
+thread pool without any threads should be an unrecoverable error. If you’re
+feeling ambitious, try to write a function named `build` with the following
+signature to compare with the `new` function:
 
 ```rust,ignore
-pub fn new(size: usize) -> Result<ThreadPool, PoolCreationError> {
+pub fn build(size: usize) -> Result<ThreadPool, PoolCreationError> {
 ```
 
 #### Creating Space to Store the Threads
 
 Now that we have a way to know we have a valid number of threads to store in
 the pool, we can create those threads and store them in the `ThreadPool` struct
-before returning it. But how do we “store” a thread? Let’s take another look at
-the `thread::spawn` signature:
+before returning the struct. But how do we “store” a thread? Let’s take another
+look at the `thread::spawn` signature:
 
 ```rust,ignore
 pub fn spawn<F, T>(f: F) -> JoinHandle<T>
@@ -351,15 +362,13 @@ using `thread::JoinHandle` as the type of the items in the vector in
 `ThreadPool`.
 
 Once a valid size is received, our `ThreadPool` creates a new vector that can
-hold `size` items. We haven’t used the `with_capacity` function in this book
-yet, which performs the same task as `Vec::new` but with an important
-difference: it preallocates space in the vector. Because we know we need to
-store `size` elements in the vector, doing this allocation up front is slightly
-more efficient than using `Vec::new`, which resizes itself as elements are
-inserted.
+hold `size` items. The `with_capacity` function performs the same task as
+`Vec::new` but with an important difference: it preallocates space in the
+vector. Because we know we need to store `size` elements in the vector, doing
+this allocation up front is slightly more efficient than using `Vec::new`,
+which resizes itself as elements are inserted.
 
-When you run `cargo check` again, you’ll get a few more warnings, but it should
-succeed.
+When you run `cargo check` again, it should succeed.
 
 #### A `Worker` Struct Responsible for Sending Code from the `ThreadPool` to a Thread
 
@@ -374,10 +383,11 @@ implement it manually.
 
 We’ll implement this behavior by introducing a new data structure between the
 `ThreadPool` and the threads that will manage this new behavior. We’ll call
-this data structure `Worker`, which is a common term in pooling
-implementations. Think of people working in the kitchen at a restaurant: the
-workers wait until orders come in from customers, and then they’re responsible
-for taking those orders and filling them.
+this data structure *Worker*, which is a common term in pooling
+implementations. The Worker picks up code that needs to be run and runs the
+code in the Worker’s thread. Think of people working in the kitchen at a
+restaurant: the workers wait until orders come in from customers, and then
+they’re responsible for taking those orders and filling them.
 
 Instead of storing a vector of `JoinHandle<()>` instances in the thread pool,
 we’ll store instances of the `Worker` struct. Each `Worker` will store a single
@@ -386,9 +396,9 @@ take a closure of code to run and send it to the already running thread for
 execution. We’ll also give each worker an `id` so we can distinguish between
 the different workers in the pool when logging or debugging.
 
-Let’s make the following changes to what happens when we create a `ThreadPool`.
-We’ll implement the code that sends the closure to the thread after we have
-`Worker` set up in this way:
+Here is the new process that will happen when we create a `ThreadPool`. We’ll
+implement the code that sends the closure to the thread after we have `Worker`
+set up in this way:
 
 1. Define a `Worker` struct that holds an `id` and a `JoinHandle<()>`.
 2. Change `ThreadPool` to hold a vector of `Worker` instances.
@@ -417,44 +427,50 @@ because it’s now holding `Worker` instances instead of `JoinHandle<()>`
 instances. We use the counter in the `for` loop as an argument to
 `Worker::new`, and we store each new `Worker` in the vector named `workers`.
 
-External code (like our server in *src/bin/main.rs*) doesn’t need to know the
+External code (like our server in *src/main.rs*) doesn’t need to know the
 implementation details regarding using a `Worker` struct within `ThreadPool`,
 so we make the `Worker` struct and its `new` function private. The
 `Worker::new` function uses the `id` we give it and stores a `JoinHandle<()>`
 instance that is created by spawning a new thread using an empty closure.
 
+> Note: If the operating system can’t create a thread because there aren’t
+> enough system resources, `thread::spawn` will panic. That will cause our
+> whole server to panic, even though the creation of some threads might
+> succeed. For simplicity’s sake, this behavior is fine, but in a production
+> thread pool implementation, you’d likely want to use
+> [`std::thread::Builder`][builder]<!-- ignore --> and its
+> [`spawn`][builder-spawn]<!-- ignore --> method that returns `Result` instead.
+
 This code will compile and will store the number of `Worker` instances we
 specified as an argument to `ThreadPool::new`. But we’re *still* not processing
 the closure that we get in `execute`. Let’s look at how to do that next.
 
 #### Sending Requests to Threads via Channels
 
-Now we’ll tackle the problem that the closures given to `thread::spawn` do
+The next problem we’ll tackle is that the closures given to `thread::spawn` do
 absolutely nothing. Currently, we get the closure we want to execute in the
 `execute` method. But we need to give `thread::spawn` a closure to run when we
 create each `Worker` during the creation of the `ThreadPool`.
 
-We want the `Worker` structs that we just created to fetch code to run from a
-queue held in the `ThreadPool` and send that code to its thread to run.
+We want the `Worker` structs that we just created to fetch the code to run from
+queue held in the `ThreadPool` and send that code to its thread to run.
 
-In Chapter 16, you learned about *channels*—a simple way to communicate between
-two threads—that would be perfect for this use case. We’ll use a channel to
-function as the queue of jobs, and `execute` will send a job from the
-`ThreadPool` to the `Worker` instances, which will send the job to its thread.
-Here is the plan:
+The channels we learned about in Chapter 16—a simple way to communicate between
+two threads—would be perfect for this use case. We’ll use a channel to function
+as the queue of jobs, and `execute` will send a job from the `ThreadPool` to
+the `Worker` instances, which will send the job to its thread. Here is the plan:
 
-1. The `ThreadPool` will create a channel and hold on to the sending side of
-   the channel.
-2. Each `Worker` will hold on to the receiving side of the channel.
+1. The `ThreadPool` will create a channel and hold on to the sender.
+2. Each `Worker` will hold on to the receiver.
 3. We’ll create a new `Job` struct that will hold the closures we want to send
    down the channel.
-4. The `execute` method will send the job it wants to execute down the sending
-   side of the channel.
-5. In its thread, the `Worker` will loop over its receiving side of the channel
-   and execute the closures of any jobs it receives.
+4. The `execute` method will send the job it wants to execute through the
+   sender.
+5. In its thread, the `Worker` will loop over its receiver and execute the
+   closures of any jobs it receives.
 
-Let’s start by creating a channel in `ThreadPool::new` and holding the sending
-side in the `ThreadPool` instance, as shown in Listing 20-16. The `Job` struct
+Let’s start by creating a channel in `ThreadPool::new` and holding the sender
+in the `ThreadPool` instance, as shown in Listing 20-16. The `Job` struct
 doesn’t hold anything for now but will be the type of item we’re sending down
 the channel.
 
@@ -465,15 +481,15 @@ the channel.
 ```
 
 <span class="caption">Listing 20-16: Modifying `ThreadPool` to store the
-sending end of a channel that sends `Job` instances</span>
+sender of a channel that transmits `Job` instances</span>
 
 In `ThreadPool::new`, we create our new channel and have the pool hold the
-sending end. This will successfully compile, still with warnings.
+sender. This will successfully compile.
 
-Let’s try passing a receiving end of the channel into each worker as the thread
-pool creates the channel. We know we want to use the receiving end in the
-thread that the workers spawn, so we’ll reference the `receiver` parameter in
-the closure. The code in Listing 20-17 won’t quite compile yet.
+Let’s try passing a receiver of the channel into each worker as the thread pool
+creates the channel. We know we want to use the receiver in the thread that the
+workers spawn, so we’ll reference the `receiver` parameter in the closure. The
+code in Listing 20-17 won’t quite compile yet.
 
 <span class="filename">Filename: src/lib.rs</span>
 
@@ -481,11 +497,10 @@ the closure. The code in Listing 20-17 won’t quite compile yet.
 {{#rustdoc_include ../listings/ch20-web-server/listing-20-17/src/lib.rs:here}}
 ```
 
-<span class="caption">Listing 20-17: Passing the receiving end of the channel
-to the workers</span>
+<span class="caption">Listing 20-17: Passing the receiver to the workers</span>
 
-We’ve made some small and straightforward changes: we pass the receiving end of
-the channel into `Worker::new`, and then we use it inside the closure.
+We’ve made some small and straightforward changes: we pass the receiver into
+`Worker::new`, and then we use it inside the closure.
 
 When we try to check this code, we get this error:
 
@@ -496,9 +511,9 @@ When we try to check this code, we get this error:
 The code is trying to pass `receiver` to multiple `Worker` instances. This
 won’t work, as you’ll recall from Chapter 16: the channel implementation that
 Rust provides is multiple *producer*, single *consumer*. This means we can’t
-just clone the consuming end of the channel to fix this code. Even if we could,
-that is not the technique we would want to use; instead, we want to distribute
-the jobs across threads by sharing the single `receiver` among all the workers.
+just clone the consuming end of the channel to fix this code. We also don’t
+want to send a message multiple times to multiple consumers; we want one list
+of messages with multiple workers such that each message gets processed once.
 
 Additionally, taking a job off the channel queue involves mutating the
 `receiver`, so the threads need a safe way to share and modify `receiver`;
@@ -516,12 +531,12 @@ receiver at a time. Listing 20-18 shows the changes we need to make.
 {{#rustdoc_include ../listings/ch20-web-server/listing-20-18/src/lib.rs:here}}
 ```
 
-<span class="caption">Listing 20-18: Sharing the receiving end of the channel
-among the workers using `Arc` and `Mutex`</span>
+<span class="caption">Listing 20-18: Sharing the receiver among the workers
+using `Arc` and `Mutex`</span>
 
-In `ThreadPool::new`, we put the receiving end of the channel in an `Arc` and a
-`Mutex`. For each new worker, we clone the `Arc` to bump the reference count so
-the workers can share ownership of the receiving end.
+In `ThreadPool::new`, we put the receiver in an `Arc` and a `Mutex`. For each
+new worker, we clone the `Arc` to bump the reference count so the workers can
+share ownership of the receiver.
 
 With these changes, the code compiles! We’re getting there!
 
@@ -531,8 +546,8 @@ Let’s finally implement the `execute` method on `ThreadPool`. We’ll also cha
 `Job` from a struct to a type alias for a trait object that holds the type of
 closure that `execute` receives. As discussed in the [“Creating Type Synonyms
 with Type Aliases”][creating-type-synonyms-with-type-aliases]<!-- ignore -->
-section of Chapter 19, type aliases allow us to make long types shorter. Look
-at Listing 20-19.
+section of Chapter 19, type aliases allow us to make long types shorter for
+ease of use. Look at Listing 20-19.
 
 <span class="filename">Filename: src/lib.rs</span>
 
@@ -577,8 +592,8 @@ you.
 
 If we get the lock on the mutex, we call `recv` to receive a `Job` from the
 channel. A final `unwrap` moves past any errors here as well, which might occur
-if the thread holding the sending side of the channel has shut down, similar to
-how the `send` method returns `Err` if the receiving side shuts down.
+if the thread holding the sender has shut down, similar to how the `send`
+method returns `Err` if the receiver shuts down.
 
 The call to `recv` blocks, so if there is no job yet, the current thread will
 wait until a job becomes available. The `Mutex<T>` ensures that only one
@@ -617,10 +632,9 @@ warning: field is never read: `thread`
 49 |     thread: thread::JoinHandle<()>,
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-warning: 3 warnings emitted
-
+warning: `hello` (lib) generated 3 warnings
     Finished dev [unoptimized + debuginfo] target(s) in 1.40s
-     Running `target/debug/main`
+     Running `target/debug/hello`
 Worker 0 got a job; executing.
 Worker 2 got a job; executing.
 Worker 1 got a job; executing.
@@ -663,8 +677,8 @@ processed. The reason is somewhat subtle: the `Mutex` struct has no public
 the `MutexGuard<T>` within the `LockResult<MutexGuard<T>>` that the `lock`
 method returns. At compile time, the borrow checker can then enforce the rule
 that a resource guarded by a `Mutex` cannot be accessed unless we hold the
-lock. But this implementation can also result in the lock being held longer
-than intended if we don’t think carefully about the lifetime of the
+lock. However, this implementation can also result in the lock being held
+longer than intended if we aren’t mindful of the lifetime of the
 `MutexGuard<T>`.
 
 The code in Listing 20-20 that uses `let job =
@@ -680,3 +694,5 @@ ch19-04-advanced-types.html#creating-type-synonyms-with-type-aliases
 [integer-types]: ch03-02-data-types.html#integer-types
 [fn-traits]:
 ch13-01-closures.html#moving-captured-values-out-of-the-closure-and-the-fn-traits
+[builder]: ../std/thread/struct.Builder.html
+[builder-spawn]: ../std/thread/struct.Builder.html#method.spawn