Mutex & RwLock
When threads must share mutable state, Mutex and RwLock serialize access so only one writer or many readers touch data at a time. Pair them with Arc for multi-thread sharing.
Search across all documentation pages
When threads must share mutable state, Mutex and RwLock serialize access so only one writer or many readers touch data at a time. Pair them with Arc for multi-thread sharing.
Quick-reference recipe card - copy-paste ready.
use std::sync::{Arc, Mutex};
fn main() {
let data = Arc::new(Mutex::new(Vec::new()));
let mut guard = data.lock().unwrap();
guard.push(1);
}When to reach for this: Shared counters, caches, or in-memory state updated by multiple threads when message passing is awkward.
use std::sync::{Arc, RwLock};
use std::thread;
#[derive(Default)]
struct Cache {
entries: RwLock<std::collections::HashMap<String, String>>,
}
fn main() {
let cache = Arc::new(Cache::default());
let mut handles = vec![];
for i in 0..8 {
let cache = Arc::clone(&cache);
handles.push(thread::spawn(move || {
let key = format!("key-{i}");
if i % 3 == 0 {
let mut map = cache.entries.write().unwrap();
map.insert(key, format!("value-{i}"));
} else {
let map = cache.entries.read().unwrap();
let _ = map.get(&key);
}
}));
}
for h in handles {
h.join().unwrap();
}
}What this demonstrates:
RwLock allows concurrent readers.write excludes all other access.Arc shares the lock across threads.lock() returns Err - recover with into_inner() or propagate failure.| Crate | Pros | Cons |
|---|---|---|
std::sync::Mutex | No extra dependency | Slightly slower, poisoning model |
parking_lot::Mutex | Faster, no poisoning | External dependency |
// Keep critical sections short
{
let mut guard = mutex.lock().unwrap();
guard.push(item);
} // guard dropped here - lock released before slow I/O
// Never hold a lock across .await in async codeMutexGuard derefs to inner data via DerefMut.parking_lot in hot paths when profiling shows lock overhead.DashMap for sharded concurrent hash maps.m1 then m2; B does reverse. Fix: Always acquire locks in a fixed global order.Mutex or channels.Arc<T> when data is immutable after construction.unwrap() on poison may hide cascading failures. Fix: Log and reset state or shut down the service.| Alternative | Use When | Don't Use When |
|---|---|---|
| Channels | Producers hand off owned work | Many threads need to read the same snapshot repeatedly |
| Atomics | Simple counters and flags | Complex data structures |
DashMap / sharded locks | High-concurrency maps | You need strict serializability on one object |
Immutable Arc + replacement | Rare updates | Frequent in-place mutation |
Use RwLock when reads are much more frequent than writes. Otherwise Mutex is simpler and often faster under write load.
A panic while holding a lock marks it poisoned. Other threads detect possible inconsistent state via lock() returning Err.
Yes. try_lock returns immediately with Ok or Err instead of blocking - useful for avoiding deadlocks or skipping work.
Smaller, faster locks without the std poisoning overhead. Common in production Rust codebases.
Use tokio::sync::Mutex for async-aware locking, or avoid locks with message passing. Never hold std::sync::MutexGuard across .await.
Load-test with many threads and measure p99 latency. If contention is high, shard data or switch to message passing.
std::sync::Mutex is not recursive. parking_lot::ReentrantMutex exists if you truly need re-entry (rare).
Some RwLock implementations starve writers or readers under extreme load. Check docs or use Mutex if fairness matters.
std::sync::Mutex still works on one thread and gives interior mutability with 'static sharing via Arc later.
When you can send messages, use atomics, or partition data so each thread owns its slice (Rayon, sharding).
Arc<Mutex<T>> patternStack 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+.
Reviewed by Chris St. John·Last updated Jul 16, 2026