Welcome to our deep dive into Rust's synchronization primitives! Today, we're going to learn about Mutex<T> and RwLock<T>, two powerful tools for managing concurrent access to shared data.
In a multi-threaded environment, when multiple threads need to access and modify shared data, it can lead to race conditions and data inconsistencies. Mutex<T> and RwLock<T> are synchronization primitives in Rust that help prevent this by controlling access to shared data.
Mutex<T> is a mutual exclusion synchronization primitive that allows only one thread to access the data at a time.RwLock<T> is a read-write synchronization primitive that allows multiple threads to read data concurrently but only one thread to write it at a time.When working with concurrent data, always prioritize using RwLock<T> over Mutex<T> if possible, as it allows for better performance by allowing multiple threads to read the data simultaneously.
Both Mutex<T> and RwLock<T> are type-parameterized, meaning they can be used with any type T.
Let's start by creating a simple Mutex and using it to manage a shared counter.
use std::sync::{Mutex, Arc};
let counter = Arc::new(Mutex::new(0));
let counter_clone = counter.clone();
let handle = thread::spawn(move || {
// This thread will increment the counter
let mut num = counter_clone.lock().unwrap();
*num += 1;
});
let handle2 = thread::spawn(move || {
// This thread will also increment the counter
let mut num = counter.lock().unwrap();
*num += 1;
});
// ... perform other operations ...
handle.join().unwrap();
handle2.join().unwrap();
let result = counter.lock().unwrap().clone();
println!("The final counter value is: {}", result);In the code above, we first create a shared counter using Mutex. We then create two threads that increment the counter. We lock the Mutex to ensure that only one thread can access the counter at a time.
Now let's take a look at how to use RwLock<T>. We'll continue with our counter example, but this time, we'll use RwLock<T> to allow multiple threads to read the counter simultaneously.
use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use std::sync::poison::PoisonError;
let counter = RwLock::new(0);
let counter_clone = counter.clone();
let handle = thread::spawn(move || {
// This thread will increment the counter
let write_guard = counter.write().unwrap();
*write_guard += 1;
});
let handle2 = thread::spawn(move || {
// This thread will read the counter
let read_guard = counter_clone.read().map_err(|_| PoisonError).unwrap();
println!("The current counter value is: {}", read_guard);
});
// ... perform other operations ...
handle.join().unwrap();
handle2.join().unwrap();In the code above, we create an RwLock for our counter. We lock the RwLock for writing, increment the counter, and then release the lock. We also lock the RwLock for reading and print the current value of the counter. The PoisonError is used to indicate that the lock has been poisoned, which means it cannot be read or written to safely anymore.
Which synchronization primitive in Rust allows multiple threads to read data concurrently but only one thread to write it at a time?
That's all for today! In the next lesson, we'll dive deeper into RwLock<T> and learn how to handle poisoned locks and deadlocks.
Stay tuned and happy coding! 🚀🚀