Concurrency Basics
9 examples to get you started with Rust concurrency - 6 basic and 3 intermediate.
Busca en todas las páginas de la documentación
9 examples to get you started with Rust concurrency - 6 basic and 3 intermediate.
std::thread.move closures before spawning threads.std::thread::spawn runs a closure on a new OS thread.
use std::thread;
fn main() {
let handle = thread::spawn(|| {
println!("hello from a thread");
});
handle.join().unwrap();
}spawn takes a 'static closure - no borrowed stack data unless scoped (see scoped threads).join blocks until the thread finishes and returns Result<T, JoinError>.join, the process may exit before the child runs.Related: Scoped Threads - borrow stack data safely
Use move to transfer ownership across thread boundaries.
use std::thread;
fn main() {
let data = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("owned data: {:?}", data);
});
handle.join().unwrap();
}move forces the closure to take ownership of captured variables.move, data is unavailable in the parent thread.Send.Related: Send & Sync - thread-safety markers
Threads can return values through join.
use std::thread;
fn main() {
let handle = thread::spawn(|| {
let sum: i32 = (1..=100).sum();
sum
});
let result = handle.join().unwrap();
println!("sum = {result}");
}JoinHandle<T>.join moves the result out of the thread.Err from join.Related: Deadlocks & Race Conditions - failure modes
Spawn several workers and join them in order.
use std::thread;
fn main() {
let handles: Vec<_> = (0..4)
.map(|i| {
thread::spawn(move || {
println!("worker {i}");
i * 10
})
})
.collect();
for handle in handles {
let n = handle.join().unwrap();
println!("got {n}");
}
}Related: Data Parallelism with Rayon - parallel iterators
Name threads for clearer logs and debugging.
use std::thread::{self, ThreadId};
fn main() {
let builder = thread::Builder::new().name("worker".into());
let handle = builder.spawn(|| {
let id: ThreadId = thread::current().id();
println!("{:?}", thread::current().name());
println!("id = {id:?}");
}).unwrap();
handle.join().unwrap();
}tracing, debuggers, and panic messages.ThreadId is opaque and useful for correlating logs.Related: Concurrency Best Practices - operational rules
Prefer channels over shared mutable state when possible.
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
tx.send("job done").unwrap();
});
let msg = rx.recv().unwrap();
println!("{msg}");
}mpsc = multiple producer, single consumer.send moves ownership of the message to the receiver.recv blocks until a message arrives.Related: Channels - full channel guide
When sharing state, wrap it in a lock.
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
handles.push(thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
}));
}
for handle in handles {
handle.join().unwrap();
}
println!("count = {}", *counter.lock().unwrap());
}Arc shares ownership across threads; Mutex serializes mutation.lock().unwrap() panics if the mutex is poisoned after a panic in another thread.Related: Mutex & RwLock - locking patterns
Many readers, few writers benefit from RwLock.
use std::sync::{Arc, RwLock};
use std::thread;
fn main() {
let cache = Arc::new(RwLock::new(String::from("seed")));
let mut handles = vec![];
for i in 0..5 {
let cache = Arc::clone(&cache);
handles.push(thread::spawn(move || {
let data = cache.read().unwrap();
println!("reader {i}: {data}");
}));
}
{
let mut data = cache.write().unwrap();
data.push_str(" updated");
}
for handle in handles {
handle.join().unwrap();
}
}read guards can coexist.write blocks all readers and other writers.Related: Arc for Shared State - sharing patterns
Threads for CPU parallelism; async for many concurrent I/O waits.
// CPU-bound: threads or rayon
// use rayon::prelude::*;
// I/O-bound: async runtime (Tokio)
// #[tokio::main]
// async fn main() { ... }spawn_blocking).Related: Async Basics - when to go async
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.
Revisado por Chris St. John·Última actualización: 16 jul 2026