We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Fearless Concurrency: Threads, Channels, Shared State step 1 of 24
Threads, spawn and join
Write a function that starts n threads. Thread i (for i from 0 to
n - 1) computes i * i as a u64 and hands it back. Return the squares in
index order.
pub fn spawn_squares(n: usize) -> Vec<u64>
- The result has exactly
nelements, and elementiisi * i. n = 0returns an empty vector,[].n = 1returns[0].n = 8returns[0, 1, 4, 9, 16, 25, 36, 49].- The tests use
n <= 256, so every square fits easily in au64. The grader runs your code on your own machine, and the limit keeps the number of operating-system threads small.
Compute each square inside the thread started for it. The exercise is about getting a value out of a thread and back to the caller.
Starting a thread and waiting for it
std::thread::spawn takes a closure, starts a new operating-system thread
that runs it, and immediately returns a JoinHandle<T>, where T is the
closure's return type. The calling thread does not wait; both threads now run
concurrently.
A handle is how you collect the result:
use std::thread;
let handle = thread::spawn(|| 6 * 7);
let answer: i32 = handle.join().unwrap(); // waits for the thread to finish
assert_eq!(answer, 42);
join does two things. It blocks: the calling thread stops at that line
until the spawned thread has finished. And it returns the closure's value,
wrapped in a std::thread::Result<T>. That Result is Ok(value) when the
closure returned normally and Err(...) when the thread panicked. Squaring a
number below 256 cannot panic, so unwrap or expect is reasonable here.
Dropping a handle detaches the thread
If you drop a JoinHandle without calling join, the thread is detached:
it keeps running, but nothing can wait for it or read its result any more.
Detaching is legal and sometimes intended, so the default compiler lints and
the default Clippy lints do not report it. For example, this compiles without
a warning:
thread::spawn(move || expensive(input)); // handle dropped: detached
Detached threads have a second consequence. When the program's main thread
returns from main, the whole process exits, and any threads still running
are stopped without finishing. A program that spawns work and forgets to join
it can therefore exit before that work has happened, without any error.
Rust's type system prevents data races in safe code, but it does not check that you waited for a thread whose result you need. The starter code has exactly this problem: it spawns every thread, drops every handle, and returns an empty vector.
Getting a deterministic order
The operating system decides when each thread runs, so threads can finish in
any order, and that order can change from run to run. If each thread pushed
its result into a shared collection, the order of that collection would vary,
and a test expecting [0, 1, 4, 9] could fail on some runs.
Joining avoids that problem when you do it in two phases:
- spawn all
nthreads, storing the handles in aVecin index order; - then go through that
Vecfrom front to back, callingjoinon each handle and collecting the values.
The output order is now the order you call join, which you control, not the
order in which threads finish. If thread 3 finishes before thread 0, its value
simply waits in its handle until you reach it.
Keep the two phases separate. If one loop spawns a thread and immediately joins it, each thread finishes before the next one starts, so no two threads ever run at the same time. The output would be correct, but the threads would add cost without allowing any concurrency.
Printing from inside a thread does not count as returning a value: the grader
reads only the vector your function returns, so each square has to come back
through join.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Rust reference solution
Sign in to track your attempts toward unlocking the reference solution.