Rust Concurrency
Based on the standard library std::thread, std::sync, and std::sync::atomic modules, along with the Async Book. Use when designing, debugging, load-testing, or reviewing threaded and async Rust code; cancellation, task ownership, lock scope, runtime sizing, queues, overload management, message passing, hand basic ownership to rust-stable and unsafe invariants to rust-unsafe-ffi.
Capability Boundaries
✅ Strengths
- OS threads (
thread::spawn, Builder, join, scoped threads, move closures)
- Synchronization primitives (Mutex, RwLock, Barrier, Condvar, OnceLock, LazyLock)
- Atomic types (AtomicBool/Isize/Usize, load/store/fetch_add/swap/compare_exchange, Ordering)
- Channels (
mpsc: multi-producer single-consumer, Receiver, Sender)
Send / Sync trait system (automatic derivation and manual implementation)
- async/await syntax with the Future trait
- Tokio runtime (
tokio::main, tokio::spawn, select!, JoinSet)
- Async I/O foundations (
tokio::fs, tokio::net, tokio::io)
- Bounded queues, backpressure, slow consumers, concurrency limits and overload strategies
- Task supervision, connection lifecycles, cancellation safety and graceful shutdown
- CPU-bound data parallelism and dedicated Rayon pools
- Crossbeam channels, queues, work-stealing deques, and scoped threads
- Read-heavy snapshots, sharded maps, caches, and alternative locks when measurements justify them
- Loom model checking and Tokio runtime diagnostics
⚠️ Prerequisites
- Understanding Rust ownership model (
rust-stable)
❌ Inapplicable Scenarios
- Unsafe code concurrent execution → use
rust-unsafe-ffi skill
- Basic ownership/borrowing → use
rust-stable skill
When to Use
- "Process data with multiple threads"
- "How to write async/await"
- "Tokio runtime usage"
- "Shared data between threads"
- "Avoid data races"
- "Rate limiting and graceful shutdown in high-concurrency services"
- "Tokio channel backlog or slow consumers"
Data Privacy
This skill does not collect, store, or transmit any user data.
I. OS Threads
use std::thread;
let handle = thread::spawn(move || {
println!("Hello from thread!");
});
handle.join().unwrap();
// Thread with configuration
let builder = thread::Builder::new()
.name("worker".into())
.stack_size(1024 * 1024);
let handle = builder.spawn(move || { /* ... */ }).unwrap();
// scoped threads (1.63+)
let mut v = vec![1, 2, 3];
thread::scope(|s| {
s.spawn(|| {
v.push(4); // borrow, no move required
});
});
println!("{v:?}"); // v remains usable
II. Synchronization Primitives
use std::sync::{Arc, Mutex, RwLock, Barrier, OnceLock, LazyLock};
// Mutex (mutual exclusion lock)
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;
}));
}
// RwLock (read-write lock)
let data = Arc::new(RwLock::new(vec![1, 2, 3]));
{
let read = data.read().unwrap();
assert_eq!(read.len(), 3);
} // Drop the read guard before taking the write lock.
data.write().unwrap().push(4);
// OnceLock (thread-safe lazy initialization)
static CONFIG: OnceLock<String> = OnceLock::new();
let config = CONFIG.get_or_init(|| load_config());
// LazyLock
static CACHE: LazyLock<HashMap<String, Data>> = LazyLock::new(HashMap::new);
III. Atomic Operations
use std::sync::atomic::{
AtomicBool, AtomicU64, Ordering
};
static COUNTER: AtomicU64 = AtomicU64::new(0);
COUNTER.fetch_add(1, Ordering::SeqCst);
static READY: AtomicBool = AtomicBool::new(false);
READY.store(true, Ordering::Release);
let ready = READY.load(Ordering::Acquire);
// Ordering levels
// Relaxed — no ordering guarantees (only atomicity)
// Release — write visibility
// Acquire — read visibility
// AcqRel — both reads and writes visible
// SeqCst — global sequential order (strongest, but not automatically default; explicit Ordering required for atomic operations)
IV. Channels
use std::sync::mpsc;
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
tx.send(1).unwrap();
tx.send(2).unwrap();
});
for received in rx {
println!("Got: {received}");
}
// Multi-producer scenario
let (tx, rx) = mpsc::channel();
let tx1 = tx.clone();
V. async/await
use tokio::time;
async fn do_work(id: u32) -> &'static str {
time::sleep(time::Duration::from_secs(1)).await;
println!("Task {id} done");
"ok"
}
#[tokio::main]
async fn main() {
// Concurrent execution
let (r1, r2) = tokio::join!(do_work(1), do_work(2));
// select!
tokio::select! {
result = do_work(1) => println!("task1: {result}"),
result = do_work(2) => println!("task2: {result}"),
}
// tokio::spawn
let handle = tokio::spawn(do_work(3));
handle.await.unwrap();
}
VI. Send / Sync
// T is Send if its ownership can be transferred across threads
// &T is Sync if it can be shared references across threads
// Types that are both Send + Sync: Arc<Mutex<T>>, i32, &'static str
// !Send types: Rc<T>, *const T
// !Sync types: RefCell<T>, Cell<T>
// Manual implementations are unsafe contracts. Do not add them merely to
// satisfy a compiler error; prove aliasing, lifetime, and thread-safety first.
VII. Select the Execution Model
| Workload |
Default starting point |
Avoid |
| Many readiness-driven network operations |
Tokio tasks with bounded admission |
One task or buffer per unbounded input |
| CPU-heavy independent items |
Rayon parallel iterators or a dedicated pool |
Running long CPU work on Tokio workers |
| Blocking filesystem, FFI, or legacy APIs |
Bounded spawn_blocking submissions or a dedicated pool |
Treating Tokio's blocking queue as backpressure |
| Synchronous MPMC messaging or work stealing |
Crossbeam channels, queues, or deques |
Selecting lock-free structures without measurement |
| Small shared state with short critical sections |
std::sync locks |
Holding guards across .await or callbacks |
| Read-mostly immutable snapshots |
ArcSwap after profiling |
A concurrent map for every read-heavy value |
| Shared keyed mutable state |
Sharded ownership or DashMap after contention tests |
Multi-key operations without an atomicity design |
| Expiring concurrent cache |
Moka with explicit capacity and eviction policy |
An unbounded map called a cache |
Tokio is primarily for I/O concurrency; Rayon is for CPU parallelism. Mixing them requires an explicit handoff, independent concurrency limits, and shutdown ownership. Read Concurrency Tool Selection before introducing a third-party primitive.
Workflow
- Classify the workload — separate readiness-driven I/O, CPU parallelism, blocking calls, synchronization, and durable messaging before selecting a runtime or primitive.
- Write concurrency budgets — define maximum connections, in-flight tasks, queue capacity, item size, timeouts, memory, CPU pools, and shutdown deadlines.
- Determine state ownership — prefer partitioned or single-writer ownership; share state only with an explicit atomicity and lock-ordering contract.
- Select communication semantics — choose bounded point-to-point, request/reply, latest-value, lossy broadcast, or durable replay deliberately; specify queue-full and receiver-lag behavior.
- Supervise execution — retain task or thread handles, propagate failure, contain panic, prevent orphan work, and define caller-cancellation behavior.
- Design graceful shutdown — stop admission, close producers, publish cancellation, join within a deadline, flush required state, and return unresolved failures.
- Measure before tuning — record throughput, p50/p95/p99 latency, queue depth, saturation, task poll time, wakeups, lock wait, CPU, allocations, and RSS.
- Verify the model — test overload and cancellation, use Loom for small synchronization state machines, and use tokio-console or tracing for runtime stalls. Read Concurrency Testing and Diagnostics.
Gotchas
- Mutex::lock() returns a
MutexGuard; do not await before dropping to avoid deadlocks
- tokio::spawn's Future must be both Send and 'static; non-Send references will cause compilation errors
- Async closures capture ownership differently than regular closures — use the move keyword explicitly for transfer of state
- Cancelled Futures in select! branches do not execute cleanup logic directly before dropping
- Atomic Ordering is not relational semantics; misuse of Relaxed can lead to unexpected memory ordering issues
- broadcast lag is distinct from normal success paths; must choose between discarding, rebuilding snapshots, disconnecting slow consumers, or persistently replaying events
- max_blocking_threads limits only the number of blocking threads and does not provide backpressure for submission queues; high-cost tasks require Semaphore or bounded queues
- JoinSet returns results in completion order; if API requires input ordering, carry indices through to restore sequence during aggregation
DashMap, parking_lot, ArcSwap, and lock-free queues change semantics as well as performance; benchmarks do not replace invariant review
- Loom sees only synchronization performed through Loom-aware types and can suffer state-space explosion; keep models small and deterministic
- Rayon work may outlive the async caller unless cancellation and pool ownership are designed explicitly
On-Demand Resources
- Concurrency Examples
- Type & Tool Quick Reference
- Concurrency Tool Selection: Read when choosing Tokio, Rayon, Crossbeam, locks, sharded maps, snapshots, or caches.
- Concurrency Testing and Diagnostics: Read when proving synchronization correctness, diagnosing runtime stalls, or load-testing overload and shutdown.
- Production Async Service Patterns: Read when designing actors, backpressure, slow consumers, task supervision, runtime configuration, and shutdown protocols.
examples/golden-threads/: CI-built scoped thread examples
Official References
1---2name: rust-concurrency3description: Design, implement, diagnose, and test Rust concurrency and parallelism with threads, Send and Sync, locks, atomics, channels, Tokio, Rayon, Crossbeam, bounded backpressure, actor ownership, task supervision, graceful shutdown, runtime diagnostics, and Loom model tests. Use when users ask about shared state, deadlocks, async tasks, CPU parallelism, high concurrency, daemon resource budgets, slow consumers, worker pools, lock-free structures, or concurrent correctness.4---56# Rust Concurrency78> Based on the standard library `std::thread`, `std::sync`, and `std::sync::atomic` modules, along with the Async Book. Use when designing, debugging, load-testing, or reviewing threaded and async Rust code; cancellation, task ownership, lock scope, runtime sizing, queues, overload management, message passing, hand basic ownership to rust-stable and unsafe invariants to rust-unsafe-ffi.910## Capability Boundaries1112### ✅ Strengths131. OS threads (`thread::spawn`, `Builder`, `join`, scoped threads, move closures)142. Synchronization primitives (Mutex, RwLock, Barrier, Condvar, OnceLock, LazyLock)153. Atomic types (AtomicBool/Isize/Usize, load/store/fetch_add/swap/compare_exchange, Ordering)164. Channels (`mpsc`: multi-producer single-consumer, Receiver, Sender)175. `Send` / `Sync` trait system (automatic derivation and manual implementation)186. async/await syntax with the Future trait197. Tokio runtime (`tokio::main`, `tokio::spawn`, select!, JoinSet)208. Async I/O foundations (`tokio::fs`, `tokio::net`, `tokio::io`)219. Bounded queues, backpressure, slow consumers, concurrency limits and overload strategies2210. Task supervision, connection lifecycles, cancellation safety and graceful shutdown2311. CPU-bound data parallelism and dedicated Rayon pools2412. Crossbeam channels, queues, work-stealing deques, and scoped threads2513. Read-heavy snapshots, sharded maps, caches, and alternative locks when measurements justify them2614. Loom model checking and Tokio runtime diagnostics2728### ⚠️ Prerequisites291. Understanding Rust ownership model (`rust-stable`)3031### ❌ Inapplicable Scenarios321. Unsafe code concurrent execution → use `rust-unsafe-ffi` skill332. Basic ownership/borrowing → use `rust-stable` skill3435## When to Use3637- "Process data with multiple threads"38- "How to write async/await"39- "Tokio runtime usage"40- "Shared data between threads"41- "Avoid data races"42- "Rate limiting and graceful shutdown in high-concurrency services"43- "Tokio channel backlog or slow consumers"4445## Data Privacy4647This skill does not collect, store, or transmit any user data.4849---5051## I. OS Threads5253```rust54use std::thread;5556let handle = thread::spawn(move || {57 println!("Hello from thread!");58});59handle.join().unwrap();6061// Thread with configuration62let builder = thread::Builder::new()63 .name("worker".into())64 .stack_size(1024 * 1024);65let handle = builder.spawn(move || { /* ... */ }).unwrap();6667// scoped threads (1.63+)68let mut v = vec![1, 2, 3];69thread::scope(|s| {70 s.spawn(|| {71 v.push(4); // borrow, no move required72 });73});74println!("{v:?}"); // v remains usable75```7677## II. Synchronization Primitives7879```rust80use std::sync::{Arc, Mutex, RwLock, Barrier, OnceLock, LazyLock};8182// Mutex (mutual exclusion lock)83let counter = Arc::new(Mutex::new(0));84let mut handles = vec![];8586for _ in 0..10 {87 let counter = Arc::clone(&counter);88 handles.push(thread::spawn(move || {89 let mut num = counter.lock().unwrap();90 *num += 1;91 }));92}9394// RwLock (read-write lock)95let data = Arc::new(RwLock::new(vec![1, 2, 3]));96{97 let read = data.read().unwrap();98 assert_eq!(read.len(), 3);99} // Drop the read guard before taking the write lock.100data.write().unwrap().push(4);101102// OnceLock (thread-safe lazy initialization)103static CONFIG: OnceLock<String> = OnceLock::new();104let config = CONFIG.get_or_init(|| load_config());105106// LazyLock107static CACHE: LazyLock<HashMap<String, Data>> = LazyLock::new(HashMap::new);108```109110## III. Atomic Operations111112```rust113use std::sync::atomic::{114 AtomicBool, AtomicU64, Ordering115};116117static COUNTER: AtomicU64 = AtomicU64::new(0);118COUNTER.fetch_add(1, Ordering::SeqCst);119120static READY: AtomicBool = AtomicBool::new(false);121READY.store(true, Ordering::Release);122let ready = READY.load(Ordering::Acquire);123124// Ordering levels125// Relaxed — no ordering guarantees (only atomicity)126// Release — write visibility127// Acquire — read visibility128// AcqRel — both reads and writes visible129// SeqCst — global sequential order (strongest, but not automatically default; explicit Ordering required for atomic operations)130```131132## IV. Channels133134```rust135use std::sync::mpsc;136137let (tx, rx) = mpsc::channel();138thread::spawn(move || {139 tx.send(1).unwrap();140 tx.send(2).unwrap();141});142for received in rx {143 println!("Got: {received}");144}145146// Multi-producer scenario147let (tx, rx) = mpsc::channel();148let tx1 = tx.clone();149```150151## V. async/await152153```rust154use tokio::time;155156async fn do_work(id: u32) -> &'static str {157 time::sleep(time::Duration::from_secs(1)).await;158 println!("Task {id} done");159 "ok"160}161162#[tokio::main]163async fn main() {164 // Concurrent execution165 let (r1, r2) = tokio::join!(do_work(1), do_work(2));166167 // select!168 tokio::select! {169 result = do_work(1) => println!("task1: {result}"),170 result = do_work(2) => println!("task2: {result}"),171 }172173 // tokio::spawn174 let handle = tokio::spawn(do_work(3));175 handle.await.unwrap();176}177```178179## VI. Send / Sync180181```rust182// T is Send if its ownership can be transferred across threads183// &T is Sync if it can be shared references across threads184185// Types that are both Send + Sync: Arc<Mutex<T>>, i32, &'static str186// !Send types: Rc<T>, *const T187// !Sync types: RefCell<T>, Cell<T>188189// Manual implementations are unsafe contracts. Do not add them merely to190// satisfy a compiler error; prove aliasing, lifetime, and thread-safety first.191```192193## VII. Select the Execution Model194195| Workload | Default starting point | Avoid |196|---|---|---|197| Many readiness-driven network operations | Tokio tasks with bounded admission | One task or buffer per unbounded input |198| CPU-heavy independent items | Rayon parallel iterators or a dedicated pool | Running long CPU work on Tokio workers |199| Blocking filesystem, FFI, or legacy APIs | Bounded `spawn_blocking` submissions or a dedicated pool | Treating Tokio's blocking queue as backpressure |200| Synchronous MPMC messaging or work stealing | Crossbeam channels, queues, or deques | Selecting lock-free structures without measurement |201| Small shared state with short critical sections | `std::sync` locks | Holding guards across `.await` or callbacks |202| Read-mostly immutable snapshots | `ArcSwap` after profiling | A concurrent map for every read-heavy value |203| Shared keyed mutable state | Sharded ownership or `DashMap` after contention tests | Multi-key operations without an atomicity design |204| Expiring concurrent cache | Moka with explicit capacity and eviction policy | An unbounded map called a cache |205206Tokio is primarily for I/O concurrency; Rayon is for CPU parallelism. Mixing them requires an explicit handoff, independent concurrency limits, and shutdown ownership. Read [Concurrency Tool Selection](references/concurrency-tool-selection.md) before introducing a third-party primitive.207208## Workflow2092101. **Classify the workload** — separate readiness-driven I/O, CPU parallelism, blocking calls, synchronization, and durable messaging before selecting a runtime or primitive.2112. **Write concurrency budgets** — define maximum connections, in-flight tasks, queue capacity, item size, timeouts, memory, CPU pools, and shutdown deadlines.2123. **Determine state ownership** — prefer partitioned or single-writer ownership; share state only with an explicit atomicity and lock-ordering contract.2134. **Select communication semantics** — choose bounded point-to-point, request/reply, latest-value, lossy broadcast, or durable replay deliberately; specify queue-full and receiver-lag behavior.2145. **Supervise execution** — retain task or thread handles, propagate failure, contain panic, prevent orphan work, and define caller-cancellation behavior.2156. **Design graceful shutdown** — stop admission, close producers, publish cancellation, join within a deadline, flush required state, and return unresolved failures.2167. **Measure before tuning** — record throughput, p50/p95/p99 latency, queue depth, saturation, task poll time, wakeups, lock wait, CPU, allocations, and RSS.2178. **Verify the model** — test overload and cancellation, use Loom for small synchronization state machines, and use tokio-console or tracing for runtime stalls. Read [Concurrency Testing and Diagnostics](references/concurrency-testing-and-diagnostics.md).218219## Gotchas2202211. Mutex::lock() returns a `MutexGuard`; do not await before dropping to avoid deadlocks2222. tokio::spawn's Future must be both Send and 'static; non-Send references will cause compilation errors2233. Async closures capture ownership differently than regular closures — use the move keyword explicitly for transfer of state2244. Cancelled Futures in select! branches do not execute cleanup logic directly before dropping2255. Atomic Ordering is not relational semantics; misuse of Relaxed can lead to unexpected memory ordering issues2266. broadcast lag is distinct from normal success paths; must choose between discarding, rebuilding snapshots, disconnecting slow consumers, or persistently replaying events2277. max_blocking_threads limits only the number of blocking threads and does not provide backpressure for submission queues; high-cost tasks require Semaphore or bounded queues2288. JoinSet returns results in completion order; if API requires input ordering, carry indices through to restore sequence during aggregation2299. `DashMap`, `parking_lot`, `ArcSwap`, and lock-free queues change semantics as well as performance; benchmarks do not replace invariant review23010. Loom sees only synchronization performed through Loom-aware types and can suffer state-space explosion; keep models small and deterministic23111. Rayon work may outlive the async caller unless cancellation and pool ownership are designed explicitly232233## On-Demand Resources234235- [Concurrency Examples](examples/examples.md)236- [Type & Tool Quick Reference](references/references.md)237- [Concurrency Tool Selection](references/concurrency-tool-selection.md): Read when choosing Tokio, Rayon, Crossbeam, locks, sharded maps, snapshots, or caches.238- [Concurrency Testing and Diagnostics](references/concurrency-testing-and-diagnostics.md): Read when proving synchronization correctness, diagnosing runtime stalls, or load-testing overload and shutdown.239- [Production Async Service Patterns](references/production-async-services.md): Read when designing actors, backpressure, slow consumers, task supervision, runtime configuration, and shutdown protocols.240- `examples/golden-threads/`: CI-built scoped thread examples241242## Official References243244- [std::thread Documentation](https://doc.rust-lang.org/std/thread/)245- [std::sync Documentation](https://doc.rust-lang.org/std/sync/)246- [std::sync::atomic Documentation](https://doc.rust-lang.org/std/sync/atomic/)247- [Async Book](https://rust-lang.github.io/async-book/)248- [Tokio Guide](https://tokio.rs/tokio/tutorial)249- [Rayon](https://docs.rs/rayon/)250- [Crossbeam](https://docs.rs/crossbeam/)251- [Loom](https://docs.rs/loom/)