Advanced Projects

Lock-Free Data Structure Fundamentals

A lock-free data structure coordinates concurrent state transitions through hardware atomic instructions (compare_exchange_weak, fetch_add, Acquire/Release memory orderings) rather than mutual exclusion locks, guaranteeing system-wide progress: even if an arbitrary thread is suspended mid-operation by the OS scheduler, other threads continue completing operations.

Treiber Stack and SPSC Ring Buffer Architecture

1. Lock-Free Treiber Push (AtomicPtr<Node<T>>):
   loop {
       old_head = head.load(Relaxed);
       new_node.next = old_head;
       if head.compare_exchange_weak(old_head, new_node, Release, Relaxed).is_ok() {
           break;
       }
   }

2. Lock-Free Single-Producer Single-Consumer (SPSC) Ring Buffer:
   Producer owns `tail` (writes slot, then stores `tail + 1` with Release)
   Consumer owns `head` (loads `tail` with Acquire, reads slot, stores `head + 1` with Release)
Memory Ordering Role in Lock-Free Protocol Hardware Guarantee
Ordering::Release Used on successful store/CAS publishing a node or ring slot Prevents prior payload writes from being reordered after the pointer/index update
Ordering::Acquire Used when reading the published pointer or index Ensures all payload writes performed before the matching Release are visible
Ordering::Relaxed Used on initial speculative loads or failed CAS retries Atomic access to the single variable without cross-variable happens-before synchronization

Complete Lock-Free SPSC Ring Buffer and Treiber Stack Implementation

use std::cell::UnsafeCell;
use std::mem::MaybeUninit;
use std::ptr;
use std::sync::Arc;
use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
use std::thread;

pub struct SpscRingBuffer<T, const N: usize> {
    buffer: [UnsafeCell<MaybeUninit<T>>; N],
    head: AtomicUsize,
    tail: AtomicUsize,
}

unsafe impl<T: Send, const N: usize> Sync for SpscRingBuffer<T, N> {}
unsafe impl<T: Send, const N: usize> Send for SpscRingBuffer<T, N> {}

impl<T, const N: usize> SpscRingBuffer<T, N> {
    pub const fn new() -> Self {
        assert!(N >= 2 && N.is_power_of_two(), "N must be a power of two >= 2");
        Self {
            // SAFETY: An array of `UnsafeCell<MaybeUninit<T>>` needs no initialization.
            buffer: unsafe { MaybeUninit::uninit().assume_init() },
            head: AtomicUsize::new(0),
            tail: AtomicUsize::new(0),
        }
    }

    pub fn try_push(&self, value: T) -> Result<(), T> {
        let tail = self.tail.load(Ordering::Relaxed);
        let head = self.head.load(Ordering::Acquire);
        if tail.wrapping_sub(head) == N {
            return Err(value);
        }
        let slot = tail & (N - 1);
        // SAFETY: Producer uniquely owns `slot` while `tail - head < N`.
        unsafe {
            (*self.buffer[slot].get()).write(value);
        }
        self.tail.store(tail.wrapping_add(1), Ordering::Release);
        Ok(())
    }

    pub fn try_pop(&self) -> Option<T> {
        let head = self.head.load(Ordering::Relaxed);
        let tail = self.tail.load(Ordering::Acquire);
        if head == tail {
            return None;
        }
        let slot = head & (N - 1);
        // SAFETY: Consumer uniquely owns `slot` after `Acquire` load observed `head != tail`.
        let value = unsafe { (*self.buffer[slot].get()).assume_init_read() };
        self.head.store(head.wrapping_add(1), Ordering::Release);
        Some(value)
    }
}

impl<T, const N: usize> Drop for SpscRingBuffer<T, N> {
    fn drop(&mut self) {
        while self.try_pop().is_some() {}
    }
}

struct Node<T> {
    value: T,
    next: *mut Node<T>,
}

pub struct AppendOnlyTreiberBag<T> {
    head: AtomicPtr<Node<T>>,
}

impl<T> AppendOnlyTreiberBag<T> {
    pub const fn new() -> Self {
        Self {
            head: AtomicPtr::new(ptr::null_mut()),
        }
    }

    pub fn push(&self, value: T) {
        let new_node = Box::into_raw(Box::new(Node {
            value,
            next: ptr::null_mut(),
        }));
        let mut current = self.head.load(Ordering::Relaxed);
        loop {
            // SAFETY: `new_node` is uniquely owned by this thread until CAS succeeds.
            unsafe {
                (*new_node).next = current;
            }
            match self.head.compare_exchange_weak(
                current,
                new_node,
                Ordering::Release,
                Ordering::Relaxed,
            ) {
                Ok(_) => break,
                Err(actual) => current = actual,
            }
        }
    }

    pub fn into_vec(mut self) -> Vec<T> {
        // Exclusive `&mut self` via ownership transfer eliminates ABA reclamation hazards on drain
        let mut curr = *self.head.get_mut();
        *self.head.get_mut() = ptr::null_mut();
        let mut out = Vec::new();
        while !curr.is_null() {
            // SAFETY: `curr` was allocated via `Box::into_raw` in `push`.
            let boxed = unsafe { Box::from_raw(curr) };
            out.push(boxed.value);
            curr = boxed.next;
        }
        out
    }
}

impl<T> Drop for AppendOnlyTreiberBag<T> {
    fn drop(&mut self) {
        let mut curr = *self.head.get_mut();
        while !curr.is_null() {
            unsafe {
                let boxed = Box::from_raw(curr);
                curr = boxed.next;
            }
        }
    }
}

fn main() {
    // 1. Lock-free SPSC ring buffer transferring 1,000 items between producer and consumer
    let ring = Arc::new(SpscRingBuffer::<u64, 64>::new());
    let ring_prod = Arc::clone(&ring);
    let ring_cons = Arc::clone(&ring);

    let producer = thread::spawn(move || {
        for i in 1..=1000 {
            let mut val = i;
            while let Err(returned) = ring_prod.try_push(val) {
                val = returned;
                thread::yield_now();
            }
        }
    });

    let consumer = thread::spawn(move || {
        let mut sum = 0u64;
        let mut received = 0usize;
        while received < 1000 {
            if let Some(v) = ring_cons.try_pop() {
                sum += v;
                received += 1;
            } else {
                thread::yield_now();
            }
        }
        sum
    });

    producer.join().unwrap();
    let spsc_sum = consumer.join().unwrap();
    println!("SPSC ring buffer sum (1..=1000) = {}", spsc_sum);

    // 2. Lock-free multi-producer Treiber bag
    let bag = Arc::new(AppendOnlyTreiberBag::new());
    thread::scope(|s| {
        for t in 0..4 {
            let b = Arc::clone(&bag);
            s.spawn(move || {
                for i in 0..25 {
                    b.push(t * 100 + i);
                }
            });
        }
    });

    let items = Arc::try_unwrap(bag).ok().unwrap().into_vec();
    println!(
        "Treiber bag collected {} items across 4 lock-free producer threads",
        items.len()
    );
}
SPSC ring buffer sum (1..=1000) = 500500
Treiber bag collected 100 items across 4 lock-free producer threads