<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Diya</title>
    <description>The latest articles on DEV Community by Diya (@diya_eed3b08acbaa5cd7175e).</description>
    <link>https://dev.to/diya_eed3b08acbaa5cd7175e</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4238423%2Fbda8ece0-d948-4c56-8a9e-555b7d1bebb6.png</url>
      <title>DEV Community: Diya</title>
      <link>https://dev.to/diya_eed3b08acbaa5cd7175e</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/diya_eed3b08acbaa5cd7175e"/>
    <language>en</language>
    <item>
      <title>Building a Lock-Free Single-Producer Single-Consumer (SPSC) Ring Buffer in C++20: Memory Orders and False Sharing Elimination</title>
      <dc:creator>Diya</dc:creator>
      <pubDate>Wed, 11 Nov 2026 06:18:38 +0000</pubDate>
      <link>https://dev.to/diya_eed3b08acbaa5cd7175e/building-a-lock-free-single-producer-single-consumer-spsc-ring-buffer-in-c20-memory-orders-and-53p5</link>
      <guid>https://dev.to/diya_eed3b08acbaa5cd7175e/building-a-lock-free-single-producer-single-consumer-spsc-ring-buffer-in-c20-memory-orders-and-53p5</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.tourl"&gt;&lt;/a&gt; In high-performance concurrent systems, real-time audio processing pipelines, and low-latency financial order routers, passing messages between threads using standard mutex-protected queues (std::queue wrapped in std::mutex) introduces severe performance penalties. Mutex locks force kernel context switches, thread blocking, and unpredictable latency spikes.&lt;/p&gt;

&lt;p&gt;To achieve lock-free inter-thread communication with nanosecond-level determinism, we must engineer a Single-Producer Single-Consumer (SPSC) Ring Buffer using C++20 atomic memory order semantics and strict cache-line alignment.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Limitations of Mutexes in Concurrent Queues
When a producer thread and a consumer thread contend for a mutex-protected queue:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Kernel Context Switches: If a thread fails to acquire a lock, the operating system suspends it, triggering an expensive transition between user space and kernel space.&lt;/p&gt;

&lt;p&gt;Cache Coherency Invalidation: Mutex locks and condition variables constantly invalidate CPU cache lines across different cores, causing high interconnect traffic.&lt;/p&gt;

&lt;p&gt;Unpredictable Latencies: P99/P999 tail latencies blow out because of scheduling jitter and lock contention.&lt;/p&gt;

&lt;p&gt;Lock-Free SPSC Solution: By guaranteeing that exactly one thread produces items and exactly one thread consumes them, we can eliminate locks entirely, replacing them with atomic indices and memory barriers.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Atomic Memory Orders (acquire-release Semantics)
In modern multi-core processors, CPU out-of-order execution and compiler reordering can cause memory writes to become visible in unpredictable sequences. We enforce strict visibility rules using C++20 atomic memory orders:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;std::memory_order_relaxed: No synchronization or ordering constraints; only atomic atomicity.&lt;/p&gt;

&lt;p&gt;std::memory_order_acquire: Ensures no reads or writes in the current thread can be reordered before this load.&lt;/p&gt;

&lt;p&gt;std::memory_order_release: Ensures no reads or writes in the current thread can be reordered after this store.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Production-Ready C++20 SPSC Ring Buffer Core
Here is a complete, production-ready C++20 implementation of a lock-free SPSC ring buffer featuring cache-line padding to prevent false sharing and power-of-two bitmask indexing:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;C++&lt;/p&gt;

&lt;h1&gt;
  
  
  include 
&lt;/h1&gt;

&lt;h1&gt;
  
  
  include 
&lt;/h1&gt;

&lt;h1&gt;
  
  
  include 
&lt;/h1&gt;

&lt;h1&gt;
  
  
  include 
&lt;/h1&gt;

&lt;h1&gt;
  
  
  include 
&lt;/h1&gt;

&lt;h1&gt;
  
  
  include 
&lt;/h1&gt;

&lt;p&gt;template &lt;br&gt;
requires (Capacity &amp;gt; 0 &amp;amp;&amp;amp; (Capacity &amp;amp; (Capacity - 1)) == 0) // Capacity must be power of 2&lt;br&gt;
class SPSCRingBuffer {&lt;br&gt;
private:&lt;br&gt;
    // Pad cache lines independently to eliminate false sharing between producer and consumer cores&lt;br&gt;
    alignas(64) std::atomic head_{0};&lt;br&gt;
    alignas(64) std::atomic tail_{0};&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;alignas(64) T buffer_[Capacity];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;public:&lt;br&gt;
    SPSCRingBuffer() = default;&lt;br&gt;
    ~SPSCRingBuffer() = default;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Non-copyable, non-movable
SPSCRingBuffer(const SPSCRingBuffer&amp;amp;) = delete;
SPSCRingBuffer&amp;amp; operator=(const SPSCRingBuffer&amp;amp;) = delete;

bool push(const T&amp;amp; item) noexcept {
    const size_t current_head = head_.load(std::memory_order_relaxed);
    const size_t current_tail = tail_.load(std::memory_order_acquire);

    // Check if ring buffer is full (Capacity mask optimization)
    if ((current_head - current_tail) &amp;gt;= Capacity) {
        return false;
    }

    buffer_[current_head &amp;amp; (Capacity - 1)] = item;

    // Release memory barrier: ensures item is written before head index is updated
    head_.store(current_head + 1, std::memory_order_release);
    return true;
}

bool pop(T&amp;amp; item) noexcept {
    const size_t current_tail = tail_.load(std::memory_order_relaxed);
    const size_t current_head = head_.load(std::memory_order_acquire);

    // Check if ring buffer is empty
    if (current_tail == current_head) {
        return false;
    }

    item = buffer_[current_tail &amp;amp; (Capacity - 1)];

    // Release memory barrier: ensures item is read before tail index is updated
    tail_.store(current_tail + 1, std::memory_order_release);
    return true;
}

bool empty() const noexcept {
    return head_.load(std::memory_order_relaxed) == tail_.load(std::memory_order_relaxed);
}

size_t size() const noexcept {
    size_t head = head_.load(std::memory_order_relaxed);
    size_t tail = tail_.load(std::memory_order_relaxed);
    return (head &amp;gt;= tail) ? (head - tail) : (Capacity - (tail - head));
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;};&lt;/p&gt;

&lt;p&gt;// =========================================================&lt;br&gt;
// Example Execution Driver&lt;br&gt;
// =========================================================&lt;br&gt;
int main() {&lt;br&gt;
    constexpr size_t QueueCapacity = 1024; // Must be power of 2&lt;br&gt;
    SPSCRingBuffer ring_buffer;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;std::atomic&amp;lt;bool&amp;gt; producer_done{false};
uint64_t total_items_to_send = 100'000;

std::cout &amp;lt;&amp;lt; "Starting Lock-Free SPSC Ring Buffer Benchmark...\n";

// Producer Thread
std::thread producer([&amp;amp;]() {
    for (uint64_t i = 1; i &amp;lt;= total_items_to_send; ++i) {
        while (!ring_buffer.push(i)) {
            std::this_thread::yield(); // Backoff if buffer is full
        }
    }
    producer_done.store(true, std::memory_order_release);
});

// Consumer Thread
std::thread consumer([&amp;amp;]() {
    uint64_t received_count = 0;
    uint64_t val = 0;
    while (!producer_done.load(std::memory_order_acquire) || !ring_buffer.empty()) {
        if (ring_buffer.pop(val)) {
            received_count++;
        } else {
            std::this_thread::yield();
        }
    }
    std::cout &amp;lt;&amp;lt; "Consumer successfully received " &amp;lt;&amp;lt; received_count &amp;lt;&amp;lt; " items with zero locks.\n";
});

producer.join();
consumer.join();

std::cout &amp;lt;&amp;lt; "Lock-Free SPSC Queue concurrency test completed successfully.\n";
return 0;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Conclusion &amp;amp; Performance Impact
By migrating from mutex-protected queues to a Lock-Free SPSC Ring Buffer in C++20:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Zero Mutex Contention: Elimination of kernel locks drops push/pop latencies down to single-digit nanoseconds.&lt;/p&gt;

&lt;p&gt;False Sharing Prevention: alignas(64) padding on atomic indices prevents cache-line bouncing between producer and consumer CPU cores.&lt;/p&gt;

&lt;p&gt;Deterministic Throughput: Powers high-speed inter-thread messaging engines where predictable, low-latency execution is non-negotiable.(&lt;a href="https://www.blogger.com/blog/post/edit/preview/4750431103251998125/6904450898359926599" rel="noopener noreferrer"&gt;https://www.blogger.com/blog/post/edit/preview/4750431103251998125/6904450898359926599&lt;/a&gt;)&lt;/p&gt;

</description>
      <category>algorithms</category>
      <category>cpp</category>
      <category>performance</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
