Course contentsShow
← Rust
Lesson 1662 of 1,91014. System Integration and EcosystemFree lesson

Lock-Free Data Structures

You will implement high-throughput primitives using atomic operations and memory barriers.

In this lesson, you will learn how to design high-throughput data structures by replacing heavy-duty mutual exclusion locks with fine-grained atomic operations and explicit memory barriers.

Moving Beyond Mutexes

In standard Rust development, we rely on Mutex<T> or RwLock<T> to ensure thread safety. While safe, these mechanisms force thread suspension when contention occurs, leading to context switches that destroy performance in high-frequency systems. A lock-free data structure ensures that at least one thread in the system always makes progress, regardless of the state of other threads.

Implementing a Lock-Free Stack

The cornerstone of lock-free design is the Compare-and-Swap (CAS) operation, provided in Rust via AtomicPtr or AtomicUsize. Instead of locking the entire stack, we attempt to update the "head" pointer using an atomic compare_exchange loop.

use std::sync::atomic::{AtomicPtr, Ordering};
use std::ptr;

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

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

impl<T> LockFreeStack<T> {
    pub fn push(&self, val: T) {
        let new_node = Box::into_raw(Box::new(Node { data: val, next: ptr::null_mut() }));
        loop {
            let old_head = self.head.load(Ordering::Relaxed);
            unsafe { (*new_node).next = old_head; }
            
            // Atomically attempt to point head to the new node
            if self.head.compare_exchange(
                old_head, new_node, 
                Ordering::Release, 
                Ordering::Relaxed
            ).is_ok() {
                break;
            }
        }
    }
}

The Role of Memory Barriers

Notice the Ordering parameters. Unlike Mutex, which implies a full memory fence, atomic operations allow us to choose the strictness of memory visibility.

  • Ordering::Relaxed is used for the load to minimize overhead.
  • Ordering::Release ensures that the initialization of the new_node happens before the pointer is published to other threads.
  • Ordering::Acquire (used in popping operations) ensures that once a thread observes the new pointer, it also sees the data written to that node.

This "lock-free" approach eliminates the kernel-level blocking of a Mutex, allowing your data structure to scale linearly with the number of CPU cores.

Key Takeaway: Lock-free structures achieve high throughput by using atomic Compare-and-Swap loops to manage state transitions, relying on hardware-level memory ordering rather than OS-level thread suspension.