When to use: I/O-bound operations (network, filesystem).
Trade-offs: Requires async runtime, function coloring.
Workflow
Step 1: Choose Concurrency Model
CPU-intensive task?
→ Use threads (rayon for data parallelism)
I/O-intensive task?
→ Use async/await (tokio, async-std)
Both?
→ Use async with spawn_blocking for CPU work
use tokio::join;
// Wait for all to completelet (result1, result2, result3) = tokio::join!(
fetch_user(),
fetch_posts(),
fetch_comments()
);
// First to completeletresult = tokio::select! {
r = fetch_from_primary() => r,
r = fetch_from_backup() => r,
};
Timeout and Cancellation
use tokio::time::{timeout, Duration};
matchtimeout(Duration::from_secs(5), long_operation()).await {
Ok(result) => result,
Err(_) => {
// Operation timed out
}
}
Review Checklist
When reviewing concurrent code:
All shared data properly synchronized (Arc/Mutex/RwLock)
Send/Sync bounds satisfied for types crossing threads
No locks held across await points
Consistent lock ordering to prevent deadlocks
Appropriate choice between threads and async
Message passing channels used correctly (no deadlocks)
Atomic operations used for simple shared state
Thread pool sized appropriately for workload
Error handling for lock poisoning
Graceful shutdown and resource cleanup
Verification Commands
# Check compilation with thread safety
cargo check
# Run tests with thread sanitizer (requires nightly)
RUSTFLAGS="-Z sanitizer=thread" cargo +nightly test# Test with miri (detect undefined behavior)
cargo +nightly miri test# Use loom for exhaustive concurrency testing
cargo test --features loom
# Check for race conditions
cargo clippy -- -W clippy::mutex_atomic
Common Pitfalls
1. Rc in Multi-threaded Context
Symptom: E0277 error, Rc cannot be sent between threads
Fix: Replace Rc with Arc
// ❌ Badletdata = Rc::new(value);
thread::spawn(move || { /* use data */ });
// ✅ Goodletdata = Arc::new(value);
thread::spawn(move || { /* use data */ });
2. Lock Across Await Points
Symptom: Deadlock or "future cannot be sent between threads safely"
Symptom: Borrow checker errors when spawning threads
Fix: Clone Arc before moving into closure
// ❌ Badletdata = Arc::new(vec![1, 2, 3]);
thread::spawn(move || { /* data moved */ });
// data is gone// ✅ Goodletdata = Arc::new(vec![1, 2, 3]);
letdata_clone = Arc::clone(&data);
thread::spawn(move || { /* data_clone moved */ });
// data still available