Creating the Thread Pool and Storing Threads - Creating the Thread Pool and Storing Threads - The Rust Programming Language

## Creating the Thread Pool and Storing Threads

The warnings are because we aren’t doing anything with the parameters to `new` and `execute`. Let’s implement the bodies of both of these with the actual behavior we want.

### Validating the Number of Threads in the Pool

To start, let’s think about `new`. We mentioned before that we picked an unsigned type for the `size` parameter since a pool with a negative number of threads makes no sense. However, a pool with zero threads also makes no sense, yet zero is a perfectly valid `u32`. Let’s check that `size` is greater than zero before we return a `ThreadPool` instance and panic if we get zero by using the `assert!` macro as shown in Listing 20-13:

Filename: src/lib.rs

    # #![allow(unused_variables)]
    #fn main() {
    # pub struct ThreadPool;
    impl ThreadPool {
        /// Create a new ThreadPool.
        ///
        /// The size is the number of threads in the pool.
        ///
        /// # Panics
        ///
        /// The `new` function will panic if the size is zero.
        pub fn new(size: u32) -> ThreadPool {
            assert!(size > 0);

            ThreadPool
        }

        // --snip--
    }
    #}

Listing 20-13: Implementing `ThreadPool::new` to panic if `size` is zero

We’ve taken this opportunity to add some documentation for our `ThreadPool` with doc comments. Note that we followed good documentation practices and added a section that calls out the situations in which our function can panic as we discussed in Chapter 14. Try running `cargo doc --open` and clicking on the `ThreadPool` struct to see what the generate docs for `new` look like!

Instead of adding the use of the `assert!` macro as we’ve done here, we could make `new` return a `Result` instead 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 this signature to see how you feel about both versions:

    fn new(size: u32) -> Result<ThreadPool, PoolCreationError> {

### Storing Threads in the Pool

Now that we know we have a valid number of threads to store in the pool, we can actually create that many threads and store them in the `ThreadPool` struct before returning it.

This raises a question: how do we “store” a thread? Let’s take another look at the signature of `thread::spawn`:

    pub fn spawn<F, T>(f: F) -> JoinHandle<T>
        where
            F: FnOnce() -> T + Send + 'static,
            T: Send + 'static

`spawn` returns a `JoinHandle<T>`, where `T` is the type that’s returned from the closure. Let’s try using `JoinHandle` too and see what happens. In our case, the closures we’re passing to the thread pool will handle the connection and not return anything, so `T` will be the unit type `()`.

This won’t compile yet, but let’s consider the code shown in Listing 20-14. We’ve changed the definition of `ThreadPool` to hold a vector of `thread::JoinHandle<()>` instances, initialized the vector with a capacity of `size`, set up a `for` loop that will run some code to create the threads, and returned a `ThreadPool` instance containing them:

Filename: src/lib.rs

    use std::thread;

    pub struct ThreadPool {
        threads: Vec<thread::JoinHandle<()>>,
    }

    impl ThreadPool {
        // --snip--
        pub fn new(size: u32) -> ThreadPool {
            assert!(size > 0);

            let mut threads = Vec::with_capacity(size);

            for _ in 0..size {
                // create some threads and store them in the vector
            }

            ThreadPool {
                threads
            }
        }

        // --snip--
    }

Listing 20-14: Creating a vector for `ThreadPool` to hold the threads

We’ve brought `std::thread` into scope in the library crate, since we’re using `thread::JoinHandle` as the type of the items in the vector in `ThreadPool`.

After we have a valid size, we’re creating a new vector that can hold `size` items. We haven’t used `with_capacity` in this book yet; it does the same thing as `Vec::new`, but with an important difference: it pre-allocates space in the vector. Since we know that we need to store `size` elements in the vector, doing this allocation up-front is slightly more efficient than only writing `Vec::new`, since `Vec::new` resizes itself as elements get inserted. Since we’ve created a vector the exact size that we need up front, no resizing of the underlying vector will happen while we populate the items.

That is, if this code works, which it doesn’t quite yet! If we check this code, we get an error:

    $ cargo check
       Compiling hello v0.1.0 (file:///projects/hello)
    error[E0308]: mismatched types
      --> src\main.rs:70:46
       |
    70 |         let mut threads = Vec::with_capacity(size);
       |                                              ^^^^ expected usize, found u32

    error: aborting due to previous error

`size` is a `u32`, but `Vec::with_capacity` needs a `usize`. We have two options here: we can change our function’s signature, or we can cast the `u32` as a `usize`. If you remember when we defined `new`, we didn’t think too hard about what number type made sense, we just chose one. Let’s give it some more thought now. Given that `size` is the length of a vector, `usize` makes a lot of sense. They even almost share a name! Let’s change the signature of `new`, which will get the code in Listing 20-14 to compile:

    fn new(size: usize) -> ThreadPool {

If run `cargo check` again, you’ll get a few more warnings, but it should succeed.

We left a comment in the `for` loop in Listing 20-14 regarding the creation of threads. How do we actually create threads? This is a tough question. What should go in these threads? We don’t know what work they need to do at this point, since the `execute` method takes the closure and gives it to the pool.

Let’s refactor slightly: instead of storing a vector of `JoinHandle<()>` instances, let’s create a new struct to represent the concept of a worker. A worker will be what receives a closure in the `execute` method, and it will take care of actually calling the closure. In addition to letting us store a fixed `size` number of `Worker` instances that don’t yet know about the closures they’re going to be executing, we can also give each worker an `id` so we can tell the different workers in the pool apart when logging or debugging.

Let’s make these changes:

1. Define a `Worker` struct that holds an `id` and a `JoinHandle<()>`
2. Change `ThreadPool` to hold a vector of `Worker` instances
3. Define a `Worker::new` function that takes an `id` number and returns a `Worker` instance with that `id` and a thread spawned with an empty closure, which we’ll fix soon
4. In `ThreadPool::new`, use the `for` loop counter to generate an `id`, create a new `Worker` with that `id`, and store the worker in the vector

If you’re up for a challenge, try implementing these changes on your own before taking a look at the code in Listing 20-15.

Ready? Here’s Listing 20-15 with one way to make these modifications:

Filename: src/lib.rs

    # #![allow(unused_variables)]
    #fn main() {
    use std::thread;

    pub struct ThreadPool {
        workers: Vec<Worker>,
    }

    impl ThreadPool {
        // --snip--
        pub fn new(size: usize) -> ThreadPool {
            assert!(size > 0);

            let mut workers = Vec::with_capacity(size);

            for id in 0..size {
                workers.push(Worker::new(id));
            }

            ThreadPool {
                workers
            }
        }
        // --snip--
    }

    struct Worker {
        id: usize,
        thread: thread::JoinHandle<()>,
    }

    impl Worker {
        fn new(id: usize) -> Worker {
            let thread = thread::spawn(|| {});

            Worker {
                id,
                thread,
            }
        }
    }
    #}

Listing 20-15: Modifying `ThreadPool` to hold `Worker` instances instead of threads directly

We’ve chosen to change the name of the field on `ThreadPool` from `threads` to `workers` since we’ve changed what we’re holding, which is now `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`.

The `Worker` struct and its `new` function are private since external code (like our server in src/bin/main.rs) doesn’t need to know the implementation detail that we’re using a `Worker` struct within `ThreadPool`. The `Worker::new` function uses the given `id` and stores a `JoinHandle<()>` created by spawning a new thread using an empty closure.

This code compiles and is storing the number of `Worker` instances that we specified as an argument to `ThreadPool::new`, but we’re still not processing the closure that we get in `execute`. Let’s talk about how to do that next.