Sling Academy
Home/Rust/Rust - Protecting concurrent writes to a shared Vector with Mutex or RwLock

Rust - Protecting concurrent writes to a shared Vector with Mutex or RwLock

Last updated: January 04, 2025

In Rust, handling concurrent access to shared resources is crucial to ensure data integrity and prevent race conditions. Two common concurrency primitives provided by Rust for this purpose are Mutex and RwLock. This article explores how to protect concurrent writes to a shared Vec using these synchronization mechanisms.

Understanding the Shared Resource

A Vec in Rust is a dynamic array that can grow and shrink. However, when multiple threads attempt to modify a Vec simultaneously, it can lead to data races or corrupted data. To prevent these problems, Rust provides mechanisms like Mutex and RwLock that help in managing concurrent access safely.

Using Mutex for Safe Access

A Mutex is a mutual exclusion primitive that ensures only one thread can access the data at any given time. It's perfect for cases where only a single writer is needed at a time.

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let numbers = Arc::new(Mutex::new(vec![]));
    let mut handles = vec![];

    for i in 0..5 {
        let numbers = Arc::clone(&numbers);
        let handle = thread::spawn(move || {
            let mut numbers = numbers.lock().unwrap();
            numbers.push(i);
            println!("Thread {} added number {}", i, i);
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Final numbers: {:?}", *numbers.lock().unwrap());
}

In this example, the Arc<Mutex<Vec<i32>>> is used to store numbers, ensuring that only one thread may modify the Vec at any one time, thus preventing race conditions.

Using RwLock for Concurrent Access

An RwLock enables multiple readers or a single writer at a time, and is useful when read operations vastly outnumber write operations, which improves concurrency while maintaining safe access.

use std::sync::{Arc, RwLock};
use std::thread;

fn main() {
    let numbers = Arc::new(RwLock::new(vec![]));
    let mut handles = vec![];

    for i in 0..5 {
        let numbers = Arc::clone(&numbers);
        let handle = thread::spawn(move || {
            {
                // Write lock for exclusive access
                let mut numbers = numbers.write().unwrap();
                numbers.push(i);
                println!("Thread {} wrote number {}", i, i);
            }
            // Read lock allows concurrent access
            let numbers = numbers.read().unwrap();
            println!("Thread {} read the vector: {:?}", i, *numbers);
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Final numbers: {:?}", *numbers.read().unwrap());
}

In the RwLock version, although only one thread can write to the vector at once, multiple threads can read concurrently. This design decision helps when the application frequently accesses data in read-only mode.

Choosing Between Mutex and RwLock

Choosing between Mutex and RwLock depends on the data access patterns you expect. If primarily read access is required with occasional writes, then RwLock is beneficial. However, if there are frequent updates, a Mutex can be a simpler and sometimes more efficient solution.

Conclusion

In concurrent Rust programming, choosing the correct synchronization primitive between Mutex and RwLock is key to maintaining performance and data safety. Understanding the trade-offs can help you protect your shared vectors effectively. Depending on whether you need more read concurrency or simpler write safety determines the best choice for your use-case.

Next Article: Rust - Designing efficient algorithms around contiguous data in Vec

Previous Article: Rust: Immutably sharing vectors with Arc> across threads

Series: Collections in Rust

Rust

You May Also Like

  • E0557 in Rust: Feature Has Been Removed or Is Unavailable in the Stable Channel
  • Network Protocol Handling Concurrency in Rust with async/await
  • Using the anyhow and thiserror Crates for Better Rust Error Tests
  • Rust - Investigating partial moves when pattern matching on vector or HashMap elements
  • Rust - Handling nested or hierarchical HashMaps for complex data relationships
  • Rust - Combining multiple HashMaps by merging keys and values
  • Composing Functionality in Rust Through Multiple Trait Bounds
  • E0437 in Rust: Unexpected `#` in macro invocation or attribute
  • Integrating I/O and Networking in Rust’s Async Concurrency
  • E0178 in Rust: Conflicting implementations of the same trait for a type
  • Utilizing a Reactor Pattern in Rust for Event-Driven Architectures
  • Parallelizing CPU-Intensive Work with Rust’s rayon Crate
  • Managing WebSocket Connections in Rust for Real-Time Apps
  • Downloading Files in Rust via HTTP for CLI Tools
  • Mocking Network Calls in Rust Tests with the surf or reqwest Crates
  • Rust - Designing advanced concurrency abstractions using generic channels or locks
  • Managing code expansion in debug builds with heavy usage of generics in Rust
  • Implementing parse-from-string logic for generic numeric types in Rust
  • Rust.- Refining trait bounds at implementation time for more specialized behavior