liveBook · Manning

Locking

As explained in Latency

A mutex lock is an exclusive synchronization primitive that permits only one concurrent execution context to access a protected resource at a time.

Locking gives one thread at a time access to a critical section that uses a shared resource. A mutex provides this mutual exclusion: each thread acquires the same mutex before accessing the resource and releases it afterward. Protecting every access this way prevents concurrent updates from corrupting shared data. Keep the critical section short so other threads spend less time waiting. section 8.1 section 8.1.1

A shared counter

Suppose 100 threads each increment one counter. Wrap the counter in a Rust std::sync::Mutex; each thread calls lock(), increments the value while holding the guard, then leaves the scope to unlock it. The increments are safe, but they cannot execute in parallel because every thread needs the same lock. section 8.1 section 5.1

use std::sync::Mutex;
use std::thread;

fn main() {
    let counter = Mutex::new(0);
    thread::scope(|s| {
        for _ in 0..100 {
            s.spawn(|| {
                let mut counter = counter.lock().unwrap();
                *counter += 1;
                // Mutex is unlocked implicitly here.
            });
        }
    });
}

The listing shows the complete scoped-thread counter example, including where the mutex is acquired and implicitly released. section 8.1

Exclusivity has a latency cost. A thread that cannot acquire a mutex waits; blocking it and waking it involves the operating system. Contention also forces otherwise concurrent work to run serially. Lock operations add overhead of their own, and contention can bring context switches, atomic-instruction costs, and memory-system effects. The resulting delays matter especially to tail latency. section 8.1.1 section 8.2.1

Measure contention, not just lock cost

In a shared-counter benchmark, a mutex lock-and-unlock takes about 7.4 ns with one thread, about 603 ns with 10, and about 5.28 µs with 100. Measure at realistic thread counts: an inexpensive uncontended operation can become expensive when many threads need the same critical section. section 8.2.1

Choose the synchronization strategy against the workload:

  • Use a read–write lock when concurrent reads are useful; writes still require exclusive access and make readers wait. section 8.1.2
  • Partition shared data to reduce the number of clients competing for each lock. Check for skew: hot partitions can make latency worse. section 5.1
  • Consider wait-free synchronization for suitable low-latency structures, but account for the atomic operations and memory barriers needed to make concurrent access safe. section 8.6 section 8.7

Also watch for failure modes beyond raw overhead: priority inversion lets a lower-priority lock holder delay higher-priority work; convoying queues threads behind a lock; deadlock can stop progress altogether. Locking remains a straightforward way to make shared access safe, but its cost depends on contention and workload shape—not merely on how fast an uncontended lock call appears. section 8.2 section 8.2.1

How Latency covers it

How deep it goes
Defines critical sections, demonstrates Rust mutex use, and measures contention; stops short of implementing a mutex.
Examples
Uses a 100-thread shared counter, contention benchmarks, and a mutex-protected bounded queue.
What it assumes
Introduces mutexes from their shared-resource purpose while using threads and a simple counter to demonstrate access.
What you can do after
Apply it via Rust std::sync::Mutex to protect shared data, then evaluate contention before choosing an alternative.
How it connects
Connects locking to read–write locks, data partitioning, deadlock, and wait-free synchronization.

Also explained in

Compare how 18 books explain locking →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Latency. The section links point to the parts of the book it draws on.