Uncovering Rust’s Hidden Concurrency Powerhouse: park and unpark
This article explains Rust’s std::thread::park/unpark primitives, demonstrates their lost‑wake‑up pitfall, introduces crossbeam’s Parker/Unparker with a token‑based memory feature that eliminates the issue, and provides multiple code examples and usage guidelines for high‑performance thread synchronization.
std::thread::park and unpark
Calling thread::park() yields the current thread's CPU time slice and puts the thread into a sleeping state. The thread can be resumed only by obtaining its Thread handle and invoking .unpark() on that handle.
use std::thread;
use std::time::Duration;
fn main() {
let parked_thread = thread::spawn(|| {
println!("child: going to sleep… 😴");
thread::park();
println!("child: woke up, back to work!");
});
println!("main: let child sleep 2 seconds.");
thread::sleep(Duration::from_secs(2));
println!("main: time's up, wake up!");
parked_thread.thread().unpark();
parked_thread.join().unwrap();
println!("main: task done.");
}Output:
main: let child sleep 2 seconds.
child: going to sleep… 😴
(main waits 2 seconds)
main: time's up, wake up!
child: woke up, back to work!
main: task done.Lost‑wake‑up flaw
The standard park / unpark pair has no memory of a wake‑up that occurs before the target thread actually enters the sleeping state. If a producer calls unpark while the consumer is still executing code before its park call, the signal is ignored and the consumer may block forever.
Consumer checks a task queue, finds it empty.
Consumer decides to call park().
Before the consumer actually sleeps, the OS schedules the producer.
Producer enqueues a task and calls unpark().
Because the consumer has not yet parked, the unpark signal is lost.
When the consumer finally executes park(), it sleeps indefinitely.
Both std::thread::park and unpark lack a “memory” token; unpark only affects a thread that is already parked.
crossbeam::sync::Parker – a token‑based upgrade
The crossbeam crate provides Parker and Unparker, which embed a wake‑up token. The token guarantees that a premature unpark is remembered and consumed by a later park call.
Token semantics : unpark() sets a token. If a token already exists, the call is a no‑op. park() first checks for a token.
If a token exists, it is consumed and park() returns immediately.
If no token exists, the thread blocks until another thread calls unpark() and creates the token.
This eliminates the lost‑wake‑up scenario: a producer's early unpark leaves a token, and the consumer's subsequent park sees the token and returns without blocking.
Creating a Parker/Unparker pair
let p = crossbeam::sync::Parker::new();
let u = p.unparker().clone();Example 1 – basic park/unpark
use std::thread;
use std::time::Duration;
use crossbeam::sync::Parker;
fn main() {
let p = Parker::new();
let u = p.unparker().clone();
let handle = thread::spawn(move || {
println!("child: ready to park…");
p.park();
println!("child: awakened!");
});
println!("main: waiting 2 seconds…");
thread::sleep(Duration::from_secs(2));
println!("main: waking child.");
u.unpark();
handle.join().unwrap();
println!("main: child finished.");
}Example 2 – demonstrating the token (unpark before park)
use std::thread;
use std::time::Duration;
use crossbeam::sync::Parker;
fn main() {
let p = Parker::new();
let u = p.unparker().clone();
let handle = thread::spawn(move || {
println!("child: sleeping 2 seconds before park…");
thread::sleep(Duration::from_secs(2));
println!("child: now trying to park…");
p.park(); // token already set, returns immediately
println!("child: park returned instantly, no block.");
});
println!("main: unpark immediately (child not yet sleeping).");
u.unpark();
handle.join().unwrap();
println!("main: task done.");
}Example 3 – task notification with AtomicBool
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use crossbeam::sync::Parker;
fn main() {
let has_task = Arc::new(AtomicBool::new(false));
let shutdown = Arc::new(AtomicBool::new(false));
let p = Parker::new();
let u = p.unparker().clone();
let worker = thread::spawn({
let has_task = has_task.clone();
let shutdown = shutdown.clone();
move || {
loop {
p.park();
if shutdown.load(Ordering::SeqCst) {
println!("worker: shutdown signal, exiting.");
break;
}
if has_task.compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst).is_ok() {
println!("worker: got a task, processing…");
thread::sleep(Duration::from_millis(500));
println!("worker: task done.");
} else {
println!("worker: spurious wake‑up, no task.");
}
}
}
});
for i in 1..=3 {
println!("
main: dispatching task {}.", i);
has_task.store(true, Ordering::SeqCst);
u.unpark();
thread::sleep(Duration::from_secs(1));
}
println!("
main: all tasks dispatched, shutting down worker.");
shutdown.store(true, Ordering::SeqCst);
u.unpark();
worker.join().unwrap();
println!("main: worker terminated.");
}When to use Unparker
Building higher‑level synchronization primitives (e.g., lock‑free queues, semaphores) where park / unpark provide efficient waiting.
High‑performance one‑to‑one thread communication, where Unparker is lighter than a Condvar + Mutex pair or a channel.
Implementing custom executors or runtimes that need to schedule many tasks and wake them on I/O or other events.
For most application‑level concurrency, the standard library channel ( std::sync::mpsc::channel) or crossbeam_channel is simpler and safer. Direct use of Parker / Unparker is reserved for performance‑critical or library‑level code.
Core advantage – built‑in memory token
Compared with Condvar, Parker / Unparker store an internal flag: unpark() sets the flag; subsequent calls are no‑ops. park() checks the flag first.
If the flag is present, it is cleared and park() returns immediately.
If the flag is absent, the thread blocks until another thread calls unpark() and sets the flag.
This token mechanism solves the classic lost‑wake‑up problem by guaranteeing that a wake‑up issued before a thread actually parks is not lost.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Rust High-Frequency Quantitative Trading
Rust High-Frequency Quantitative Trading System
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
