RECIPE / CONCURRENCY

Lock-free Data Structures

Lock-free data structures let multiple threads make forward progress without mutual exclusion. They trade conceptual simplicity for predictable latency, making them essential in trading engines, kernel paths, and real-time systems where a single contended mutex can ruin tail latencies.

1. Atomic Primitives

The bedrock of any lock-free design is the compare-and-swap (CAS) primitive. Modern CPUs expose it via instructions like CMPXCHGon x86 and LDREX/STREXon ARM. Treat every shared word as an atomic and reason about memory ordering explicitly.

2. The ABA Problem

CAS only checks the current value, not the history. If a pointer goes A -> B -> A, a stale CAS will succeed and silently corrupt your structure. Common mitigations: tagged pointers (pack a version counter in the high bits), hazard pointers, or epoch-based reclamation. Pick one before you write a single line of code.

3. Treiber Stack

The canonical lock-free stack. Push and pop both spin on a CAS against the head pointer. Below is a minimal sketch using C++ atomics, suitable as a starting template before you layer in safe reclamation:

template<typename T>
class TreiberStack {
  struct Node { T value; Node* next; };
  std::atomic<Node*> head_{nullptr};
public:
  void push(T v) {
    Node* n = new Node{std::move(v), nullptr};
    n->next = head_.load(std::memory_order_relaxed);
    while (!head_.compare_exchange_weak(
        n->next, n,
        std::memory_order_release,
        std::memory_order_relaxed)) {}
  }
  bool pop(T& out) {
    Node* old = head_.load(std::memory_order_acquire);
    while (old && !head_.compare_exchange_weak(
        old, old->next,
        std::memory_order_acquire,
        std::memory_order_relaxed)) {}
    if (!old) return false;
    out = std::move(old->value);
    return true;
  }
};