Threads
Thread Handles
A thread handle in Rust is represented by two distinct types in std::thread:
JoinHandle<T>(andScopedJoinHandle<'scope, T>): The unique owning handle returned when spawning a thread, used to wait for the thread to finish (.join()) and retrieve its return valueT.Thread: A cloneable metadata and wakeup descriptor (accessible viahandle.thread()orstd::thread::current()) that identifies a thread'sThreadId, optional name, and.unpark()token.
Unique Join Handle (`JoinHandle<T>` — Not Clone!)
+-- .join() -> thread::Result<T> (Consumes JoinHandle, waits for completion)
+-- .is_finished() -> bool (Non-blocking completion check)
+-- .thread() -> &Thread (Borrows the underlying Thread descriptor)
|
v
Cloneable Thread Descriptor (`Thread` — Implements Clone, Send, Sync!)
+-- .id() -> ThreadId
+-- .name() -> Option<&str>
+-- .unpark() (Wakes up the thread if parked via `thread::park()`)
Dropping a JoinHandle<T> Detaches the Thread
Unlike a mutex guard or file handle, dropping a JoinHandle<T> does NOT kill or cancel the running OS thread. Instead, dropping a JoinHandle<T> detaches the thread: the thread continues running in the background until its closure finishes (or the main process exits), and the OS automatically reclaims its thread resources upon completion.
Action on JoinHandle<T> |
Effect on Background Thread | Return Value T |
|---|---|---|
let res = handle.join(); |
Blocks caller until background thread finishes | Returned inside Ok(T) (or Err(panic_payload)) |
drop(handle); (or ignoring thread::spawn(...)) |
Detaches thread; continues running in background | Dropped inside the background thread when it finishes |
handle.is_finished() |
Non-blocking poll (true if thread has exited) |
Still held inside handle until .join() is called |
Polling Completion with JoinHandle::is_finished and Parking with Thread::unpark
Stabilized in Rust 1.61, handle.is_finished() checks whether the associated thread has terminated without blocking. In addition, handle.thread().unpark() pairs with std::thread::park() to provide a lightweight per-thread wakeup mechanism:
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
static READY: AtomicBool = AtomicBool::new(false);
fn main() {
let worker = thread::Builder::new()
.name(String::from("parked-worker"))
.spawn(|| {
// Park in a loop checking the atomic condition (guards against spurious wakeups):
while !READY.load(Ordering::Acquire) {
thread::park();
}
println!(
"[{:?}] woke up and saw READY == true!",
thread::current().name().unwrap_or("unnamed")
);
99_u32
})
.unwrap();
println!("worker is_finished before unpark: {}", worker.is_finished());
// Set condition and wake the worker via its `Thread` descriptor:
READY.store(true, Ordering::Release);
worker.thread().unpark();
let output = worker.join().unwrap();
println!("worker joined with output: {}", output);
}
worker is_finished before unpark: false
["parked-worker"] woke up and saw READY == true!
worker joined with output: 99