← Back to list

Demystifying Memory Ordering in C++

If you’ve ever wondered why your lock-free queue is slower than a mutex, you will have a better view on things by the end of this article.

De Nada in Women in Technology · 2026-07-07 10:35 · 50 claps · 4.3 min read
#cplusplus #concurrency #lock-free-programming #software-development #software-engineering
Open on Medium ↗
Wiki topics: GEN · Genomics & Sequencing 💻 · Programming

Your code doesn’t necessarily get executed in the order you wrote it.

Your code doesn’t necessarily get executed in the order you wrote it.

Demystifying Memory Ordering in C++: Inaugural

If you’ve ever wondered why your lock-free queue is slower than a mutex, you will have a better view on things by the end of this article.

As a System Programming Language, C++ is all about being as “close to the machine” as possible, so close that there would be no need for lower level languages specially when it comes to Memory Manipulation and in a way that is C++’s super power!

Memory Model Basics

Starting with the basic definitions we have two main aspect categories both of which are interrelated:

  • structural aspects [related to how things are laid out in memory locations].
  • concurrency aspects [related to multiple threading].

If multiple threads try to access separate memory locations there is really no problem here. Big problems arise when threads access the same memory location without synchronization, and at least one access is a write, that causes what is commonly known as a data race which puts you in one of the nastiest corners of C++ Undefined Behaviors.

Every C++ developer knows std::atomic is a go-to fix for these issues. But most treat Memory Ordering like black magic, defaulting to std::memory_order_seq_cst and hoping for the best. This approach works, but it may be silently killing your performance.

There’s a reason lock-free legends use acquire, release, and relaxed and it’s not just to show off. In this article we will deep dive, breaking down the six memory orderings and exposing the three patterns that cover 90% of real-world concurrency to show you exactly when you can drop the safety rails without falling off.

C++ Memory Ordering: What acquire and release Actually Mean

Here’s a confession: most C++ developers (myself included, not long ago) slap std::memory_order_seq_cst on every other atomic operation and hope for the best. It works. It’s safe. But it’s also leaving performance on the table and more importantly, it means you don’t actually understand what’s happening under the hood.

This article will change that. By the end, you’ll know exactly when to use each memory order, why acquire and release form the most important pattern, and how to reason about concurrent code without guesswork.

Prerequisites: Basic familiarity with std::atomic and std::thread.

Why Memory Ordering Exists!

Your C++ code doesn’t run the way you wrote it!

Both the compiler and the CPU reorder operations to enhance performance. This is fine in single-threaded code the “as-if” rule guarantees observable behavior stays the same. But in multithreaded code? Reordering creates chaos.

// Thread 1
data = 42;
ready = true;

// Thread 2
if (ready)
    std::cout << data;  // Could print 0!

Without memory ordering, Thread 2 might see ready == true before it sees data == 42. The CPU or compiler could have reordered the stores in Thread 1, or the loads in Thread 2.

The C++ Memory Orders From Weakest to Strongest

C++ gives you six memory orders. Here they are, ranked by strength:

Order   Strength  Use Case
relaxed Weakest   Counters, statistics
consume Weak      (Avoid poorly supported)
acquire Medium    Reading shared state
release Medium    Publishing shared state
acq_rel Strong    Read-modify-write operations
seq_cst Strongest Default, full ordering

memory_order_relaxed

No synchronization. No ordering guarantees. Only atomicity is guaranteed.

std::atomic<int> counter{0};

void increment() {
    // Safe: atomic increment, but no ordering with other operations
    counter.fetch_add(1, std::memory_order_relaxed);
}

int get_count() {
    return counter.load(std::memory_order_relaxed);
}

When to use: Statistics, counters, progress indicators anything where you don’t need to synchronize other data with this value.

memory_order_consume

Skip this one. It’s designed for dependency chains but no major compiler implements it correctly. They all promote it to acquire. The C++ committee is reworking it.

memory_order_acquire and memory_order_release

This is the most important pair. Master this and you’ve mastered 90% of practical memory ordering.

Release: “I’m done writing. Publish everything before this point.” Acquire: “I’m about to read. Make sure I see everything published.”

std::atomic<bool> ready{false};
int data = 0;  // non-atomic!

// Thread 1 (Producer)
void producer() {
    data = 42;                                    // A
    ready.store(true, std::memory_order_release); // B: release barrier
}

// Thread 2 (Consumer)
void consumer() {
    while (!ready.load(std::memory_order_acquire)) {} // C: acquire barrier
    assert(data == 42);  // D: guaranteed to see 42!
}

What happens:

  • The releaseat B ensures A happens-before B
  • The acquireat C synchronizes with the releaseat B
  • Therefore, A happens-before D
  • data == 42 is guaranteed

Visual:

Thread 1:                 Thread 2:
  data = 42       
       │          │
                  │
  ready = true ───┼──► ready == true
    (release)     │       (acquire)
                  │          │
                  └──────────
                         data == 42 

memory_order_acq_rel

Combines acquire and release. Use for read-modify-write operations that both consume and publish.

std::atomic<int> ref_count{1};

void release_reference() {
    // Acquire: see all writes before we potentially delete
    // Release: our "I'm done" is visible to others
    if (ref_count.fetch_sub(1, std::memory_order_acq_rel) == 1) {
        delete resource;
    }
}

memory_order_seq_cst

Sequential consistency. The default. All threads see all seq_cstoperations in the same global order.

std::atomic<bool> x{false};
std::atomic<bool> y{false};
std::atomic<int> z{0};

void write_x() { x.store(true, std::memory_order_seq_cst); }
void write_y() { y.store(true, std::memory_order_seq_cst); }

void read_x_then_y() {
    while (!x.load(std::memory_order_seq_cst)) {}
    if (y.load(std::memory_order_seq_cst)) ++z;
}

void read_y_then_x() {
    while (!y.load(std::memory_order_seq_cst)) {}
    if (x.load(std::memory_order_seq_cst)) ++z;
}

// After all threads complete: z > 0 is GUARANTEED
// At least one reader sees both flags as true

Cost: Global synchronization is expensive. On x86 it’s not terrible, but on ARM/PowerPC it can be significant.

What’s Next

You now understand the six memory orderings and the guarantees each provides from relaxed(just atomicity) to seq_cst(global ordering). You know why your code doesn’t run the way you wrote it, and you’ve seen the release/acquirehandshake that forms the backbone of most synchronization.

But knowing the tools isn’t the same as knowing when to use them.

In the upcoming article, we’ll move from theory to practice. I’ll show you the three patterns that cover 90% of real-world concurrency: producer-consumer, reference counting, and spinlocks. We’ll build a lock-free queue, benchmark the performance difference between orderings, and give you a decision flowchart you can actually use.

Because understanding memory ordering isn’t about memorizing rules it’s about recognizing patterns.

Reference: This article draws from Chapter 5 of “C++ Concurrency in Action” by Anthony Williams (2nd Edition), specifically sections 5.2 and 5.3. If you want the full deep-dive:

  • Chapter 5 (entire): The definitive reference on the C++ memory model
  • Section 5.2: Atomic operations and types
  • Section 5.3: Synchronization and ordering constraints

메타데이터
post_id
4cb2b6aaeae8
slug
demystifying-memory-ordering-in-c-4cb2b6aaeae8
url
https://medium.com/womenintechnology/demystifying-memory-ordering-in-c-4cb2b6aaeae8
canonical_url
https://medium.com/womenintechnology/demystifying-memory-ordering-in-c-4cb2b6aaeae8
author_url
https://medium.com/@bits_and_giggles
status
ok
fetched_at
2026-07-09 16:18:44