RwLock<T>
std::sync::RwLock<T> (Reader-Writer Lock) is a synchronization primitive that distinguishes read-only access from mutating write access: any number of threads may hold shared read locks (RwLockReadGuard<'_, T>) simultaneously, whereas at most one thread may hold an exclusive write lock (RwLockWriteGuard<'_, T>) with zero active readers.
Reader-Writer State Machine
RwLock<T> Internal State Word:
[ Unlocked (readers = 0, writer = false) ]
| |
| .read() | .write()
v v
[ Shared Read Mode ] [ Exclusive Write Mode ]
- readers >= 1 - writer = true, readers = 0
- New `.read()` calls - All `.read()` and `.write()`
succeed concurrently* calls block until writer guard drops
- `.write()` blocks until
readers drop to 0
*Note: To prevent writer starvation on modern operating systems, once a thread blocks waiting on .write(), subsequent .read() calls queue behind the pending writer so a continuous stream of overlapping readers cannot lock out writers forever.
Complete RwLock<T> API Reference
| Method | Signature | Description |
|---|---|---|
RwLock::new(val) |
const fn new(t: T) -> RwLock<T> |
Creates an unlocked RwLock<T> (usable in static) |
.read(&self) |
fn read(&self) -> LockResult<RwLockReadGuard<'_, T>> |
Blocks until no writer is active and returns a shared read guard (&T) |
.try_read(&self) |
fn try_read(&self) -> TryLockResult<RwLockReadGuard<'_, T>> |
Non-blocking attempt to acquire a shared read guard |
.write(&self) |
fn write(&self) -> LockResult<RwLockWriteGuard<'_, T>> |
Blocks until all readers and writers finish and returns an exclusive write guard (&mut T) |
.try_write(&self) |
fn try_write(&self) -> TryLockResult<RwLockWriteGuard<'_, T>> |
Non-blocking attempt to acquire an exclusive write guard |
.get_mut(&mut self) |
fn get_mut(&mut self) -> LockResult<&mut T> |
Zero-cost &mut T access when caller holds exclusive &mut RwLock<T> |
.into_inner(self) |
fn into_inner(self) -> LockResult<T> |
Consumes the RwLock<T> and returns T |
Read-Heavy Configuration Cache Example
RwLock<T> outperforms Mutex<T> when reads vastly outnumber writes and each read operation does enough work (such as searching a multi-element structure or validating rules) that concurrent readers overlap significantly:
use std::collections::BTreeMap;
use std::sync::RwLock;
use std::thread;
struct FeatureFlags {
flags: RwLock<BTreeMap<String, bool>>,
}
impl FeatureFlags {
fn new() -> Self {
Self {
flags: RwLock::new(BTreeMap::from([
(String::from("dark_mode"), true),
(String::from("beta_search"), false),
])),
}
}
fn is_enabled(&self, key: &str) -> bool {
// Multiple threads execute `.read()` concurrently without blocking each other:
self.flags.read().unwrap().get(key).copied().unwrap_or(false)
}
fn set_flag(&self, key: &str, enabled: bool) {
// Exclusive `.write()` waits for active readers and updates the map:
self.flags.write().unwrap().insert(key.to_string(), enabled);
}
}
fn main() {
let store = FeatureFlags::new();
thread::scope(|s| {
for reader_id in 0..3 {
let store_ref = &store;
s.spawn(move || {
println!(
"reader #{reader_id}: dark_mode = {}",
store_ref.is_enabled("dark_mode")
);
});
}
s.spawn(|| {
store.set_flag("beta_search", true);
});
});
println!("after update: beta_search = {}", store.is_enabled("beta_search"));
}
reader #0: dark_mode = true
reader #1: dark_mode = true
reader #2: dark_mode = true
after update: beta_search = true
When Mutex<T> Is Actually Faster Than RwLock<T>
If the critical section is tiny (such as incrementing two integers or pushing a pointer onto a Vec), Mutex<T> is often faster than RwLock<T> even on read-mostly workloads because RwLock::read() still executes an atomic read-modify-write instruction on the shared reader counter (causing cache-line bouncing across CPU cores) and has more complex bookkeeping than Mutex<T>.