Rust Guide > Documentation > Concurrency > RwLock

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 poisoned RwLock will result in a PoisonError.
  • Use Arc (Atomic Reference Counting) to share a RwLock 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.