Introduction
The RwLock
class in Rust provides a reader-writer lock, which allows multiple readers or a single writer to access the data at a time. It is useful for scenarios where read-heavy workloads need concurrent access, but write operations require exclusive access.
Using RwLock
The RwLock
class can be used to create a thread-safe wrapper around a piece of data, allowing for multiple readers or a single writer. Here is a basic example:
use std::sync::{Arc, RwLock};
use std::thread;
fn main() {
let lock = Arc::new(RwLock::new(5));
let mut handles = vec![];
for _ in 0..10 {
let lock = Arc::clone(&lock);
let handle = thread::spawn(move || {
let read_guard = lock.read().unwrap();
println!("Read value: {}", *read_guard);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
}
// Output (order may vary):
// Read value: 5
// Read value: 5
// ...
Key Methods
Below are some of the key methods exposed by the RwLock
class:
new
Creates a new RwLock
instance wrapping the given data.
use std::sync::RwLock;
fn main() {
let lock = RwLock::new(5);
println!("RwLock created with initial value: {:?}", lock);
}
// Output: RwLock created with initial value: RwLock { data: 5, .. }
read
Acquires a shared lock for reading. Multiple readers can hold the lock simultaneously.
use std::sync::{Arc, RwLock};
use std::thread;
fn main() {
let lock = Arc::new(RwLock::new(5));
let lock_clone = Arc::clone(&lock);
let handle = thread::spawn(move || {
let read_guard = lock_clone.read().unwrap();
println!("Read value: {}", *read_guard);
});
handle.join().unwrap();
}
// Output: Read value: 5
write
Acquires an exclusive lock for writing. Only one writer can hold the lock at a time.
use std::sync::{Arc, RwLock};
use std::thread;
fn main() {
let lock = Arc::new(RwLock::new(5));
let lock_clone = Arc::clone(&lock);
let handle = thread::spawn(move || {
let mut write_guard = lock_clone.write().unwrap();
*write_guard += 1;
});
handle.join().unwrap();
println!("Updated value: {}", *lock.read().unwrap());
}
// Output: Updated value: 6
try_read
Attempts to acquire a shared lock for reading without blocking. Returns a Result
which is Ok(RwLockReadGuard)
if the lock was acquired, or Err(TryLockError)
if it was not.
use std::sync::{RwLock, TryLockError};
fn main() {
let lock = RwLock::new(5);
match lock.try_read() {
Ok(read_guard) => println!("Read value: {}", *read_guard),
Err(TryLockError::WouldBlock) => println!("Could not acquire read lock"),
Err(TryLockError::Poisoned(err)) => println!("Lock is poisoned: {}", err),
}
}
// Output: Read value: 5
try_write
Attempts to acquire an exclusive lock for writing without blocking. Returns a Result
which is Ok(RwLockWriteGuard)
if the lock was acquired, or Err(TryLockError)
if it was not.
use std::sync::{RwLock, TryLockError};
fn main() {
let lock = RwLock::new(5);
match lock.try_write() {
Ok(mut write_guard) => *write_guard += 1,
Err(TryLockError::WouldBlock) => println!("Could not acquire write lock"),
Err(TryLockError::Poisoned(err)) => println!("Lock is poisoned: {}", err),
}
println!("Value: {}", *lock.read().unwrap());
}
// Output: Value: 6
Example Usage
Example 1: Basic Usage
use std::sync::{Arc, RwLock};
use std::thread;
fn main() {
let lock = Arc::new(RwLock::new(5));
let mut handles = vec![];
for _ in 0..10 {
let lock = Arc::clone(&lock);
let handle = thread::spawn(move || {
let read_guard = lock.read().unwrap();
println!("Read value: {}", *read_guard);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
}
// Output (order may vary):
// Read value: 5
// Read value: 5
// ...
Example 2: Single Writer
use std::sync::{Arc, RwLock};
use std::thread;
fn main() {
let lock = Arc::new(RwLock::new(5));
let lock_clone = Arc::clone(&lock);
let handle = thread::spawn(move || {
let mut write_guard = lock_clone.write().unwrap();
*write_guard += 1;
});
handle.join().unwrap();
println!("Updated value: {}", *lock.read().unwrap());
}
// Output: Updated value: 6
Example 3: Multiple Readers and Single Writer
use std::sync::{Arc, RwLock};
use std::thread;
fn main() {
let lock = Arc::new(RwLock::new(5));
let mut handles = vec![];
for _ in 0..5 {
let lock = Arc::clone(&lock);
let handle = thread::spawn(move || {
let read_guard = lock.read().unwrap();
println!("Read value: {}", *read_guard);
});
handles.push(handle);
}
let lock_clone = Arc::clone(&lock);
let handle = thread::spawn(move || {
let mut write_guard = lock_clone.write().unwrap();
*write_guard += 1;
});
handles.push(handle);
for handle in handles {
handle.join().unwrap();
}
println!("Final value: {}", *lock.read().unwrap());
}
// Output (order may vary):
// Read value: 5
// Read value: 5
// ...
// Final value: 6
Example 4: Try Read
use std::sync::{RwLock, TryLockError};
fn main() {
let lock = RwLock::new(5);
match lock.try_read() {
Ok(read_guard) => println!("Read value: {}", *read_guard),
Err(TryLockError::WouldBlock) => println!("Could not acquire read lock"),
Err(TryLockError::Poisoned(err)) => println!("Lock is poisoned: {}", err),
}
}
// Output: Read value: 5
Example 5: Try Write
use std::sync::{RwLock, TryLockError};
fn main() {
let lock = RwLock::new(5);
match lock.try_write() {
Ok(mut write_guard) => *write_guard += 1,
Err(TryLockError::WouldBlock) => println!("Could not acquire write lock"),
Err(TryLockError::Poisoned(err)) => println!("Lock is poisoned: {}", err),
}
println!("Value: {}", *lock.read().unwrap());
}
// Output: Value: 6
Considerations
RwLock
can cause deadlocks if not used carefully. Ensure that locks are released appropriately.- A
RwLock
can become poisoned if a thread panics while holding the lock. Accessing a poisonedRwLock
will result in aPoisonError
. - Use
Arc
(Atomic Reference Counting) to share aRwLock
across multiple threads.
See Also
- Mutex - A mutual exclusion primitive useful for protecting shared data.
- Condvar - A condition variable for thread synchronization.
- AtomicUsize - A thread-safe atomic integer type.
- Arc - An atomic reference-counted smart pointer for shared ownership across threads.