Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
117: RwLock vs Mutex
I've seen a lot of developers treat Mutex as the default "make it thread-safe" button in Rust. It’s a logical instinct; you have data, you want to share it across threads, and you want to make sure nobody steps on each other's toes. But as your application scales, you might notice a strange performance plateau where adding more CPU cores doesn't actually make your app faster. Usually, the culprit is a Mutex protecting data that is read far more often than it's changed.
The Mutex Bottleneck
Imagine we're building a system that tracks user permissions for a large API. We have a HashMap that maps a User ID to a set of permission strings. Since permissions don't change every second, but are checked on every single incoming request, we wrap that map in an Arc<Mutex<HashMap<u32, Vec<String>>>>.
use std::sync::{Arc, Mutex}; use std::collections::HashMap; struct PermissionCache { data: Arc<Mutex<HashMap<u32, Vec<String>>>>, } impl PermissionCache { fn has_permission(&self, user_id: u32, perm: &str) -> bool { let lock = self.data.lock().unwrap(); lock.get(&user_id) .map(|perms| perms.contains(&perm.to_string())) .unwrap_or(false) } }On the surface, this is correct. It's safe. But here is the problem: a
Mutexis exclusive. If you have 16 cores and 16 threads all trying to check a user's permissions at the exact same time, 15 of those threads are sitting idle, blocked, waiting for their turn to read a value that isn't even changing. We've essentially turned our multi-threaded application into a single-threaded one for this specific piece of data.Opening the Gates with RwLock
This is where
RwLock(Read-Write Lock) comes in. The core philosophy is simple: reading data doesn't mutate it, so why should readers block other readers? AnRwLockallows an unlimited number of readers to hold the lock simultaneously, as long as nobody is writing. The moment a writer requests access, the lock ensures that all current readers finish up, and no new readers can enter until the writer is done.If we swap the
Mutexfor anRwLock, thehas_permissionmethod changes from.lock()to.read():use std::sync::{Arc, RwLock}; use std::collections::HashMap; struct PermissionCache { data: Arc<RwLock<HashMap<u32, Vec<String>>>>, } impl PermissionCache { fn has_permission(&self, user_id: u32, perm: &str) -> bool { // Multiple threads can call .read() at the same time! let lock = self.data.read().unwrap(); lock.get(&user_id) .map(|perms| perms.contains(&perm.to_string())) .unwrap_or(false) } fn update_permission(&self, user_id: u32, perm: String) { // This will block all readers until the update is complete let mut lock = self.data.write().unwrap(); lock.entry(user_id).or_default().push(perm); } }Now, those 16 threads can all check permissions in parallel. You've just removed a massive point of contention in your system without sacrificing memory safety.
When the "Better" Way is Actually Slower
I should warn you: don't go through your entire codebase and replace every
Mutexwith anRwLock. There's a hidden cost. AnRwLockhas to keep track of how many readers are currently active. This internal bookkeeping is more complex and computationally expensive than the simple "locked/unlocked" state of aMutex.If your data is being written to frequently—say, a counter that increments every time a packet arrives—the overhead of managing the reader count in an
RwLockwill actually make your code slower than if you'd just used aMutex. Furthermore, if you have a very high volume of writers, they can potentially "starve" the readers (or vice versa, depending on the OS implementation of the lock), leading to unpredictable latency.My rule of thumb? If your read-to-write ratio is roughly 10:1 or higher,
RwLockis your best friend. If you're writing nearly as often as you're reading, stick with the simplicity and speed of aMutex.
📋 Practical Task
Refactoring the Global Configuration Store
You have been handed a piece of legacy code for a game server. The ConfigStore holds global server settings (like max_players and server_name). Currently, it uses a Mutex, and the developers are complaining that the server lags whenever many players join and the system checks the config.
Your Task:
- Convert the
Mutexin theConfigStoreto anRwLock. - Update the
get_settingmethod to use a read lock. - Update the
set_settingmethod to use a write lock. - Ensure the code compiles and correctly handles the
Resultreturned by the lock methods.
use std::sync::{Arc, Mutex};
use std::collections::HashMap;
struct ConfigStore {
settings: Arc<Mutex<HashMap<String, String>>>,
}
impl ConfigStore {
fn new() -> Self {
Self {
settings: Arc::new(Mutex::new(HashMap::new())),
}
}
fn get_setting(&self, key: &str) -> Option<String> {
let lock = self.settings.lock().unwrap();
lock.get(key).cloned()
}
fn set_setting(&self, key: String, value: String) {
let mut lock = self.settings.lock().unwrap();
lock.insert(key, value);
}
}
fn main() {
let store = ConfigStore::new();
store.set_setting("server_name".to_string(), "Rustacean Realm".to_string());
println!("Server Name: {:?}", store.get_setting("server_name"));
}
There are no comments for now.