Synchronization Primitives

Choosing Synchronization Primitives

Selecting the right synchronization primitive in Rust minimizes contention and matches the exact sharing, mutation, and notification pattern of your concurrent workload.

Decision Tree for Multi-Threaded Synchronization

What is the multi-threaded access pattern?
  |
  +-- 1. Initialized once, then read-only forever?
  |     +-- Fixed initializer (`static` global) -----------> `LazyLock<T>`
  |     +-- Initialized at runtime with args --------------> `OnceLock<T>`
  |     +-- Heap value created first, then shared ---------> `Arc<T>` (no inner lock!)
  |
  +-- 2. Single counter, boolean flag, or bitmask?
  |     +--------------------------------------------------> `AtomicU64` / `AtomicUsize` / `AtomicBool`
  |
  +-- 3. Mutating a struct or collection (`Vec`, `HashMap`)?
  |     +-- General read/write or short critical section --> `Mutex<T>` (or `Sharded<Mutex<T>>`)
  |     +-- Expensive/frequent reads, rare writes ---------> `RwLock<T>`
  |
  +-- 4. Thread coordination & signaling?
        +-- Producer-consumer ownership transfer ----------> `mpsc::channel` / `sync_channel`
        +-- Sleep until custom predicate on `Mutex<T>` ----> `Mutex<T>` + `Condvar`
        +-- Synchronize N threads at phase boundaries -----> `Barrier`

Complete Comparison of std::sync Primitives

Primitive Protects Data T? Read Concurrency Write Concurrency Blocks OS Thread? Best Use Case
Arc<T> Yes (immutable T) Unlimited (&T directly) Copy-on-write via Arc::make_mut Never Sharing read-only configs, buffers, and services
OnceLock<T> / LazyLock<T> Yes (write-once T) Unlimited (&T directly) 1 thread once Only during first init Global statics and lazy memoized caches
AtomicBool / AtomicU64 Single primitive word Unlimited (lock-free) Lock-free (fetch_add, CAS) Never Metrics, sequence numbers, cancellation flags
Mutex<T> Yes (arbitrary T) 1 thread at a time 1 thread at a time Yes (futex on contention) General shared mutable state with short critical sections
RwLock<T> Yes (T: Send + Sync) $N$ readers simultaneously 1 writer exclusively Yes (futex on contention) Read-dominated routing tables and in-memory indexes
Condvar Paired with Mutex<T> N/A N/A Yes (until .notify_*()) Bounded queues, resource pools, state-change waiters
Barrier No (rendezvous only) N/A N/A Yes (until $N$ threads arrive) Iterative parallel simulations and phased benchmarks

Combining OnceLock, AtomicU64, and Mutex in a Service Controller

use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Mutex, OnceLock};
use std::thread;

struct ServiceController {
    // 1. Write-once startup banner -> `OnceLock` (lock-free `&str` reads):
    service_name: OnceLock<String>,
    // 2. High-frequency counter -> `AtomicU64` (wait-free hardware increments):
    requests_handled: AtomicU64,
    // 3. Mutable error log -> `Mutex<Vec<String>>`:
    recent_errors: Mutex<Vec<String>>,
}

impl ServiceController {
    const fn new() -> Self {
        Self {
            service_name: OnceLock::new(),
            requests_handled: AtomicU64::new(0),
            recent_errors: Mutex::new(Vec::new()),
        }
    }

    fn init(&self, name: &str) {
        let _ = self.service_name.set(name.to_string());
    }

    fn handle_request(&self, succeed: bool) {
        self.requests_handled.fetch_add(1, Ordering::Relaxed);
        if !succeed {
            self.recent_errors
                .lock()
                .unwrap()
                .push(String::from("500 Internal Error"));
        }
    }
}

static CONTROLLER: ServiceController = ServiceController::new();

fn main() {
    CONTROLLER.init("payment-gateway");

    thread::scope(|s| {
        s.spawn(|| CONTROLLER.handle_request(true));
        s.spawn(|| CONTROLLER.handle_request(false));
        s.spawn(|| CONTROLLER.handle_request(true));
    });

    println!(
        "service = {:?}, requests = {}, errors = {:?}",
        CONTROLLER.service_name.get().unwrap(),
        CONTROLLER.requests_handled.load(Ordering::Relaxed),
        *CONTROLLER.recent_errors.lock().unwrap()
    );
}
service = "payment-gateway", requests = 3, errors = ["500 Internal Error"]