#279 Jul 24, 2026

279. thread::park — Block a Thread Without Dragging In a Mutex

This morning’s bite (278) needed a Mutex + Condvar pair just to make one thread wait for another. When exactly one thread sleeps and you hold its handle, thread::park does it with no lock at all.

Every thread owns a park token. thread::park() blocks until the token is available, then consumes it; unpark() makes it available. That token is the whole trick:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
use std::thread;
use std::time::Duration;

let h = thread::spawn(|| {
    thread::sleep(Duration::from_millis(50));
    thread::park(); // returns instantly — token already there
});

h.thread().unpark(); // fires BEFORE the park
h.join().unwrap();

With a naive flag-and-sleep scheme, waking a thread before it goes to sleep means the wakeup is lost. The token can’t be lost: an early unpark is banked, and the next park returns immediately. That’s the race Condvar needs its Mutex to prevent — park solves it for free.

One Condvar lesson still applies, though: park may also wake spuriously, so pair it with a flag and re-check in a loop:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;

let go = Arc::new(AtomicBool::new(false));
let go2 = Arc::clone(&go);

let worker = thread::spawn(move || {
    while !go2.load(Ordering::Acquire) {
        thread::park(); // sleep until unparked
    }
    // ... do the work
});

go.store(true, Ordering::Release);
worker.thread().unpark();
worker.join().unwrap();

The flag decides whether to run; park only decides when to stop burning CPU. Store with Release, load with Acquire, and the write is visible when the loop exits — the exact handoff from bite 277.

Note what you didn’t need: no Mutex, no guard rebinding, no Condvar. The waiter is addressed directly through its Thread handle (worker.thread() — cloneable, Send, happy to be stashed anywhere).

There’s a deadline-aware sibling, thread::park_timeout(dur), for “wake me up when signaled, or after 10ms, whichever comes first” — the classic backoff loop in lock-free code.

Reach for Condvar when many threads wait on shared state; reach for park when one known thread naps until another taps it. It’s what mpsc channels and most executors use under the hood.

Stable since Rust 1.0.

← Previous 278. Condvar::wait_while — The Wait Loop You Keep Writing (and Getting Backwards) Next → 280. Mutex::get_mut — You Have &mut, Stop Paying for lock()