We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Fearless Concurrency: Threads, Channels, Shared State step 14 of 24
Keep worker-pool results in input order
Build a worker pool that computes a Collatz stopping time for each job and returns the results in the same order as the input.
pub fn parallel_map_collatz(jobs: Vec<u64>, workers: usize) -> Vec<u32>
A stopping time counts the steps needed to reach 1. Each step divides an even
value by 2 or replaces an odd value with 3 * n + 1.
jobs = [27, 1, 3, 2, 3]
result = [111, 0, 7, 1, 7]
Keep duplicate jobs as separate entries. Empty input returns an empty vector without starting workers. For nonempty input, treat zero workers as one. Use one shared job queue, the requested number of worker threads after that adjustment, and a separate channel for results. Join the workers before returning.
Jobs are positive and restricted to trajectories that reach 1, whose intermediate values fit in u64 and whose step counts fit in u32. You do not need to handle zero, arithmetic overflow or arbitrary nonterminating inputs. Worker counts are small enough to create the threads; resource-exhaustion handling is outside this exercise.
Carry the input position
Workers may finish jobs in a different order from the input. Send (index, job)
to the queue and (index, steps) back. Allocate an output vector with one slot
per job, then store each result at its original index.
For the example above, the two jobs with value 3 belong to indices 2 and 4. They must remain separate even though their results are equal. Sorting bare step counts loses the input ordering. Sorting indexed results by index could restore it, but direct placement avoids that extra work.
The fixtures compare the same inputs across different worker counts. This checks the returned sequence, but does not prove parallel speedup, exercise every thread schedule or detect every concurrency bug.
Share the receiver and release the lock
The starter moves job_rx into the first closure and tries to move it again on the next iteration. That produces E0382. A standard mpsc Receiver is not Clone or Sync. Share ownership through Arc and protect receiver access with Mutex:
type JobQueue = Arc<Mutex<Receiver<(usize, u64)>>>;
The alias uses the imports in the reference design. Give each worker an Arc clone and a result-sender clone. Receive a job inside a short lock scope:
let job = {
let guard = queue.lock().expect("queue poisoned");
guard.recv()
};
This fragment belongs inside the worker loop. The guard drops before the worker computes the stopping time. Receiving is serialized, but computation can overlap across workers. Holding the guard while computing would serialize that work too, even though the final values could still be correct.
The type alias names the shared queue; it does not change synchronization or ownership behavior. You do not need a dependency or a nightly channel API.
Let both receive loops finish
After enqueueing all jobs, drop job_tx. Workers can drain the queued jobs, then recv returns an error because no job senders remain. Break the worker loop on that error.
After spawning workers, drop the main thread's res_tx. Each worker drops its own sender when it exits. The result iterator then finishes after receiving the buffered results and observing that every sender is gone. Keeping either original sender alive would leave the corresponding receive loop waiting in this design.
See the standard Receiver documentation for blocking and disconnection behavior. Passing requires compilation, tests and Clippy with warnings denied.
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.