Atomics and Lock Free Operations
Concurrency and Multithreading

7.5 Atomics and Lock Free Operations

While mutex locks prevent race conditions, they are slow. Locking a mutex requires a system call to the operating system kernel. If the lock is held, the OS puts the thread to sleep, which triggers a context switch (saving thread registers and reloading another thread). This takes hundreds of nanoseconds.

In latency critical systems, we avoid locks entirely and use hardware supported lock free atomic operations.

The Cost of Mutex Context Switches

When a thread is put to sleep by the OS scheduler, the CPU cache lines associated with that thread are cleared to make room for the incoming thread. When your thread wakes up again, it experiences cache misses, degrading overall system execution speed.

Atomics: Hardware Level Thread Safety

Modern CPU architectures (like x86_64 and ARM) support atomic operations directly at the silicon level using hardware instructions (such as Compare And Swap, or CAS). C++ exposes this hardware capability using std::atomic (from the <atomic> header).

An atomic variable performs read, modify, and write operations as a single, uninterrupted hardware instruction. No mutex locks, no context switches, and no thread sleep calls are involved:

#include <iostream>
#include <thread>
#include <atomic>
#include <vector>

// Blazing fast: Thread safe variable running directly on CPU registers
std::atomic<int> atomicCounter{0};

void incrementAtomic() {
    for (int i{0}; i < 1000; ++i) {
        // Compiles to lock free hardware instructions (e.g. lock xadd on x86)
        ++atomicCounter;
    }
}

int main() {
    std::thread t1{incrementAtomic};
    std::thread t2{incrementAtomic};
    
    t1.join();
    t2.join();
    
    std::cout << "Atomic Counter: " << atomicCounter << '\n'; // Guaranteed 2000
    return 0;
}
High speed thread safe operations using std::atomic.

Memory Barriers and Cache Sync

Atomics do more than prevent race conditions; they enforce compiler and hardware memory barriers. When you write to an atomic variable, C++ ensures that the CPU caches across all cores synchronize their data blocks, preventing other threads from reading outdated values.

* The Sugar Jar Metaphor: Storing variables with mutexes is like a coffee shop with a locked front door. Only one customer is let in at a time to add sugar to their cup. The shop is safe, but slow. Using atomics is like putting a single self serve sugar jar on a table. Customers line up and tap it directly. The shop keeps moving at full speed, and no lock cycles are created.

Finished reading this lesson?