We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Fearless Concurrency: Threads, Channels, Shared State step 22 of 24
park and unpark: building a blocking primitive by hand
Repair a relay that passes a turn around a ring of worker threads.
pub fn relay(rounds: usize, threads: usize) -> Vec<usize>
Use t = threads.max(1): a request for zero threads uses one worker. Worker
IDs are 0..t. Worker 0 goes first, followed by 1, and so on, wrapping back
to 0. Each turn appends the worker's ID to a shared log.
Return the log after exactly rounds * t turns, and join every worker before
returning. Zero rounds produce an empty log. Inputs are small enough to create
the workers and log, and rounds * t fits in usize.
relay(3, 2) -> [0, 1, 0, 1, 0, 1]
relay(2, 4) -> [0, 1, 2, 3, 0, 1, 2, 3]
relay(3, 1) -> [0, 0, 0]
relay(2, 0) -> [0, 0]
relay(0, 4) -> []
Keep the turn in shared state
Use thread::park and Thread::unpark to wait and signal, with a Mutex
protecting the relay state. Do not use a Condvar or replace waiting with a
busy loop. These are implementation requirements for the exercise; matching
the output alone does not establish that a solution follows them.
The holder field says whose turn it is. A worker must check that field under
the mutex before appending its ID. A ring of threads alone does not guarantee
output order: the shared turn check and update do that.
Understand the parking token
Each thread has at most one parking token, initially absent. unpark makes
it available. park can consume an available token and return immediately,
or wait for one. It can also return spuriously, without a token.
Two calls to unpark do not accumulate two tokens. After every return from
park, check the shared state again; waking does not mean it is your turn.
If a handoff calls unpark after you unlock but before you call park, the
token remains available for that call. Keep this interval free of unrelated
code that might itself park and consume the token, including logging callbacks.
When park consumes a token, it synchronizes with the preceding unpark
operations for that thread. A spurious return alone provides no such signal.
In this relay, the mutex also synchronizes access to the shared fields.
Release the mutex before waiting
The starter calls park while still holding its mutex guard in two places:
while waiting for startup and while waiting for its turn.
This can deadlock. For example, a worker can acquire the mutex before the parent publishes the peer handles, then park while holding it. The parent needs that same mutex to finish startup, so it cannot reach its wakeup calls. Whether this happens depends on scheduling; successful compilation does not rule it out.
In both waiting branches, use this sequence:
lock the state
inspect startup, completion, and turn conditions
if waiting is necessary:
release the guard
park
repeat the checks
Do not simply move park outside the lock and then assume the condition is
true when it returns. Repeat the loop so spurious or earlier notifications
cannot let the wrong worker record a turn.
Publish handles before starting
The parent can obtain a Thread handle from each JoinHandle using
handle.thread().clone(). The clone is another handle to the same worker;
it does not create a thread.
After spawning the workers, the parent locks the state, installs all peer
handles, and sets started = true. It then releases the guard and unparks
the workers. No worker may relay before started is true, because handing
off requires the peer list to be ready.
Wake waiting workers at completion
A normal turn appends the current ID, increments hops, and sets the next
holder under the lock. Release the guard before notifying the next worker.
On the final turn, notify every worker so each can observe completion and
exit. Otherwise a worker may remain parked while the parent waits in join.
Check completion before waiting for another turn. With zero rounds, workers
should see completion immediately after startup.
To investigate a hang, check both waiting branches, startup publication, and the final wakeup of all workers. Repeated runs can expose scheduling bugs, but passing them is not a proof that every possible interleaving is correct.
A passing submission must compile, pass the tests, and pass
clippy -D warnings.
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.