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
- RwLockcan cause deadlocks if not used carefully. Ensure that locks are released appropriately.
- A RwLockcan become poisoned if a thread panics while holding the lock. Accessing a poisonedRwLockwill result in aPoisonError.
- Use Arc(Atomic Reference Counting) to share aRwLockacross 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.
