| name | rust-concurrency |
| description | | Use when this capability is needed. |
Quick Navigation
Rust Concurrency
Rust's ownership model prevents data races at compile time. If it compiles, it's data-race-free.
The Core Guarantee
Send: A type can be transferred to another thread
Sync: A type can be shared (via &T) across threads
// Compiler-enforced rules:
T: Send → you can move T to another thread
T: Sync → you can share &T across threads
Arc<T>: Send iff T: Send + Sync
Mutex<T>: Send iff T: Send
Threads vs Async
| std::thread | tokio::spawn |
|---|
| Best for | CPU-bound, blocking | I/O-bound, many connections |
| Cost | OS thread (~1MB stack) | Task (~few KB) |
| Blocking | Natural | Must use spawn_blocking |
| Parallelism | True parallel | Concurrent (1+ threads) |
Use threads when: crunching numbers, image processing, parsing large files.
Use async when: waiting on network, database, disk I/O.
Basic Threads
use std::thread;
use std::time::Duration;
let handle = thread::spawn(|| {
println!("Hello from thread!");
42
});
let result = handle.join().unwrap();
println!("Thread returned: {result}");
let data = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("Got: {:?}", data);
data.len()
});
Thread Scope (Borrow in Threads)
use std::thread;
let data = vec![1, 2, 3, 4, 5, 6, 7, 8];
thread::scope(|s| {
let chunk1 = &data[..4];
let chunk2 = &data[4..];
let t1 = s.spawn(|| chunk1.iter().sum::<i32>());
let t2 = s.spawn(|| chunk2.iter().sum::<i32>());
let sum = t1.join().unwrap() + t2.join().unwrap();
println!("Sum: {sum}");
});
Shared State
Arc<Mutex>
use std::sync::{Arc, Mutex};
use std::thread;
let counter = Arc::new(Mutex::new(0u64));
let handles: Vec<_> = (0..10).map(|_| {
let counter = Arc::clone(&counter);
thread::spawn(move || {
let mut guard = counter.lock().unwrap();
*guard += 1;
})
}).collect();
for h in handles { h.join().unwrap(); }
println!("Final: {}", *counter.lock().unwrap());
Pitfalls:
let guard = mutex.lock().unwrap();
some_slow_io_call();
let data = mutex.lock().unwrap().clone();
drop(data);
some_slow_io_call();
Arc<RwLock> — Many Readers, One Writer
use std::sync::{Arc, RwLock};
let config = Arc::new(RwLock::new(Config::default()));
let reader1 = {
let c = Arc::clone(&config);
thread::spawn(move || {
let guard = c.read().unwrap();
println!("timeout: {}", guard.timeout);
})
};
let writer = {
let c = Arc::clone(&config);
thread::spawn(move || {
let mut guard = c.write().unwrap();
guard.timeout = 60;
})
};
RwLock risk: writer starvation. If readers are constant, writers wait forever.
For read-heavy workloads with infrequent writes, consider arc-swap crate.
Channels (Message Passing)
std::sync::mpsc
use std::sync::mpsc;
use std::thread;
let (tx, rx) = mpsc::channel::<String>();
for i in 0..5 {
let tx = tx.clone();
thread::spawn(move || {
tx.send(format!("Message {i}")).unwrap();
});
}
drop(tx);
for msg in rx {
println!("Got: {msg}");
}
let (tx, rx) = mpsc::sync_channel::<u32>(16);
Crossbeam Channels (Better)
use crossbeam::channel::{bounded, unbounded, select};
let (tx, rx) = bounded::<Work>(100);
for i in 0..4 {
let tx = tx.clone();
thread::spawn(move || {
loop {
tx.send(Work::new(i)).unwrap();
}
});
}
for _ in 0..4 {
let rx = rx.clone();
thread::spawn(move || {
for work in &rx {
process(work);
}
});
}
select! {
recv(rx1) -> msg => handle_msg(msg),
recv(rx2) -> msg => handle_msg(msg),
recv(timeout_rx) -> _ => return Err(Timeout),
}
Rayon: Data Parallelism
use rayon::prelude::*;
let data: Vec<u64> = (0..1_000_000).collect();
let sum: u64 = data.iter().map(|&x| x * x).sum();
let sum: u64 = data.par_iter().map(|&x| x * x).sum();
let mut v: Vec<i32> = (0..1000).rev().collect();
v.par_sort();
let results: Vec<String> = names
.par_iter()
.filter(|n| n.starts_with('A'))
.map(|n| format!("Hello, {n}!"))
.collect();
let (sum, product) = rayon::join(
|| data.().sum::<>(),
|| data.().product::<>(),
);
Atomic Operations
For simple counters and flags without mutexes:
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
let counter = Arc::new(AtomicUsize::new(0));
let shutdown = Arc::new(AtomicBool::new(false));
counter.fetch_add(1, Ordering::Relaxed);
let count = counter.load(Ordering::Acquire);
shutdown.store(true, Ordering::Release);
Thread Pool
struct ThreadPool {
workers: Vec<thread::JoinHandle<()>>,
tx: crossbeam::channel::Sender<Box<dyn FnOnce() + Send>>,
}
impl ThreadPool {
fn new(n: usize) -> Self {
let (tx, rx) = crossbeam::channel::bounded::<Box<dyn FnOnce() + Send>>(64);
let rx = std::sync::Arc::new(rx);
let workers = (0..n).map(|_| {
let rx = Arc::clone(&rx);
thread::spawn(move || {
for task in rx.iter() {
task();
}
})
}).collect();
Self { workers, tx }
}
fn execute<F: FnOnce() + Send + 'static>(&, f: F) {
.tx.(::(f)).();
}
}
Debugging Concurrency Issues
Detecting Deadlocks
use parking_lot::deadlock;
thread::spawn(|| {
loop {
thread::sleep(Duration::from_secs(10));
let deadlocks = deadlock::check_deadlock();
if !deadlocks.is_empty() {
eprintln!("{} deadlocks detected", deadlocks.len());
}
}
});
Loom: Testing Concurrency
#[cfg(loom)]
use loom::sync::atomic::{AtomicUsize, Ordering};
#[cfg(loom)]
#[test]
fn test_concurrent_increment() {
loom::model(|| {
let counter = Arc::new(AtomicUsize::new(0));
let c1 = Arc::clone(&counter);
let c2 = Arc::clone(&counter);
let t1 = loom::thread::spawn(move || c1.fetch_add(1, Ordering::SeqCst));
let t2 = loom::thread::spawn(move || c2.fetch_add(1, Ordering::SeqCst));
t1.join().unwrap();
t2.join().unwrap();
assert_eq!(counter.load(Ordering::SeqCst), 2);
});
}
Common Patterns
let (tx, rx) = crossbeam::channel::bounded(1000);
let (tx, rx) = crossbeam::channel::unbounded();
let receivers: Vec<_> = (0..4).map(|_| rx.clone()).collect();
let (tx, rx) = crossbeam::channel::unbounded();
let senders: Vec<_> = (0..4).map(|_| tx.clone()).collect();
drop(tx);
let (done_tx, done_rx) = crossbeam::channel::bounded::<()>(1);
thread::spawn(move || {
do_work();
done_tx.send(()).unwrap();
});
done_rx.recv().unwrap();
Review Checklist
- Prefer message passing or ownership transfer before shared mutable state.
- Use bounded channels when producers can outpace consumers.
- Keep lock scopes small and never hold locks across blocking operations.
- Document lock ordering when multiple locks are unavoidable.
- Use atomics only with a stated ordering invariant.
- Model-check custom synchronization with
loom before trusting tests.
References
Source: adxptived/Rust-Skills — distributed by TomeVault.