Concurrency Fundamentals

Shared-State Concurrency

Shared-state concurrency is the model in which multiple threads read and mutate a common in-memory data structure coordinated by mutual-exclusion locks (Mutex<T>, RwLock<T>) or hardware atomic primitives (Atomic*).

Data-Orientated Lock Ownership in Rust

In C, C++, Java, and Go, a mutex (pthread_mutex_t, sync.Mutex) is a standalone object separate from the fields it protects; nothing in the compiler stops a developer from reading or writing a shared struct field while forgetting to lock the mutex first.

In Rust, Mutex<T> and RwLock<T> are generic containers that wrap and own the protected data T. The only way to obtain &T or &mut T is by calling .lock(), .read(), or .write(), which returns a scoped guard (MutexGuard<'_, T>) that releases the lock automatically when dropped:

C / Go Uncoupled Lock (Bug-Prone):        Rust Data-Carrying Lock (Compiler-Enforced):
+---------------+   +---------------+     +---------------------------------------+
|  sync.Mutex   |   |  balance: i64 |     |  Mutex<Account>                       |
+---------------+   +---------------+     |    +-------------------------------+  |
(Caller can touch `balance` directly      |    | Account { balance: i64 }      |  |
 without ever locking `sync.Mutex`!)      |    +-------------------------------+  |
                                          +---------------------------------------+
                                          (Cannot touch `Account` without `.lock()`!)

Comparing Message Passing and Shared-State Concurrency

Criterion Message Passing (mpsc Channels) Shared-State (Arc<Mutex<T>>, RwLock<T>, Atomic*)
Mental model Single owner per message; ownership flows along a pipeline Multiple threads access a single canonical state table
Contention behavior Producers enqueue work; consumer processes sequentially Threads contend directly on cache lines or lock queues
Memory footprint Buffers queued messages in channel memory Single in-place copy of the shared data structure
Deadlock risk Low (possible only with cyclic bounded sync_channel waits) Moderate if multiple locks are acquired in inconsistent order
Best suited for Task queues, actor pipelines, event streaming, I/O fan-in High-speed in-memory caches, shared connection pools, atomic metrics

Sharded Locking to Reduce Contention

When a shared HashMap<K, V> is accessed by many threads concurrently, wrapping the entire map in a single Mutex<HashMap<K, V>> serializes operations on unrelated keys. Lock sharding partitions keys across a fixed array of independent Mutex<HashMap<K, V>> buckets ([Mutex<HashMap<K, V>>; N]), multiplying concurrent throughput by N:

use std::collections::HashMap;
use std::hash::{DefaultHasher, Hash, Hasher};
use std::sync::Mutex;
use std::thread;

struct ShardedMap<K, V, const SHARDS: usize = 8> {
    shards: [Mutex<HashMap<K, V>>; SHARDS],
}

impl<K: Eq + Hash, V, const SHARDS: usize> ShardedMap<K, V, SHARDS> {
    pub fn new() -> Self {
        Self {
            shards: [const { Mutex::new(HashMap::new()) }; SHARDS],
        }
    }

    fn shard_index(&self, key: &K) -> usize {
        let mut hasher = DefaultHasher::new();
        key.hash(&mut hasher);
        (hasher.finish() as usize) % SHARDS
    }

    pub fn insert(&self, key: K, value: V) {
        let idx = self.shard_index(&key);
        self.shards[idx].lock().unwrap().insert(key, value);
    }

    pub fn get_cloned(&self, key: &K) -> Option<V>
    where
        V: Clone,
    {
        let idx = self.shard_index(key);
        self.shards[idx].lock().unwrap().get(key).cloned()
    }
}

fn main() {
    let map: ShardedMap<u32, String, 4> = ShardedMap::new();

    thread::scope(|s| {
        for id in 0..8 {
            s.spawn(move || {
                map.insert(id, format!("val-{id}"));
            });
        }
    });

    println!("key 3: {:?}, key 7: {:?}", map.get_cloned(&3), map.get_cloned(&7));
}
key 3: Some("val-3"), key 7: Some("val-7")