CodeNebula
C++11coreintermediate

Memory Model (C++11 Addition)

C++11 · Modern Revolution · Compiler flag: -std=c++11

Explanation

C++11 defines a formal memory model that specifies how threads interact through memory, including ordering guarantees such as `memory_order_relaxed`, `memory_order_acquire`, `memory_order_release`, and `memory_order_seq_cst`.

Real-World Scenario

Build a lock-free single-producer single-consumer queue using acquire/release semantics for correct inter-thread communication.

Quick Snippet

snippet.cpp
#include <atomic>
std::atomic<bool> flag(false);
int data = 0;

void producer() {
    data = 42;
    flag.store(true, std::memory_order_release);
}
void consumer() {
    while (!flag.load(std::memory_order_acquire)) {}
    assert(data == 42); // guaranteed by acquire/release
}

Full Example

memory_model.cpp
// CodeNebula - C++11 Example: Memory Model and Memory Ordering
// Compile: g++ -std=c++11 -Wall -Wextra -pthread -o example memory_model.cpp
//
// This example demonstrates:
// - C++11 memory ordering options (relaxed, acquire, release, seq_cst)
// - Acquire-release semantics for producer-consumer patterns
// - Relaxed ordering for simple counters
// - Sequential consistency as the default and strongest guarantee
// - Practical examples of each ordering level

#include <iostream>
#include <atomic>
#include <thread>
#include <cassert>

// ============================================================
// 1. Sequential Consistency (default, strongest)
// ============================================================
// All threads observe the same order of operations.
// This is the default for std::atomic operations.
std::atomic<bool> seqFlag1(false);
std::atomic<bool> seqFlag2(false);
std::atomic<int> seqResult(0);

void seqWriter1() {
    seqFlag1.store(true);  // Default: memory_order_seq_cst
}

void seqWriter2() {
    seqFlag2.store(true);  // Default: memory_order_seq_cst
}

void seqReader() {
    // With seq_cst, if we see flag1==true then flag2==true,
    // every thread agrees on this ordering
    while (!seqFlag1.load() || !seqFlag2.load()) {}
    seqResult.store(42);
}

// ============================================================
// 2. Acquire-Release: producer-consumer synchronization
// ============================================================
std::atomic<bool> dataReady(false);
int sharedPayload = 0;  // Non-atomic, protected by acquire-release

void releaseProducer() {
    sharedPayload = 99;  // Write data (non-atomic is fine here)
    // Release: all writes before this are visible to the acquiring thread
    dataReady.store(true, std::memory_order_release);
}

void acquireConsumer() {
    // Acquire: sees all writes that happened before the release
    while (!dataReady.load(std::memory_order_acquire)) {}
    // sharedPayload is guaranteed to be 99 here
    std::cout << "  Acquire-Release consumer got: " << sharedPayload << std::endl;
}

// ============================================================
// 3. Relaxed ordering: no synchronization guarantees
// ============================================================
// Only guarantees atomicity, not ordering between threads.
// Good for simple counters where order doesn't matter.
std::atomic<int> relaxedCounter(0);

void relaxedIncrement(int count) {
    for (int i = 0; i < count; ++i) {
        // Relaxed: just atomic increment, no ordering constraints
        relaxedCounter.fetch_add(1, std::memory_order_relaxed);
    }
}

// ============================================================
// 4. Release-Consume (simplified concept)
// ============================================================
// memory_order_consume is intended for dependency chains.
// In practice, most compilers promote it to acquire.
// Shown here for completeness.
struct Data {
    int value;
    int extra;
};

std::atomic<Data*> atomicPtr(nullptr);

void publishData() {
    Data* d = new Data{42, 100};
    // Release ensures d's fields are visible
    atomicPtr.store(d, std::memory_order_release);
}

void readData() {
    Data* d = nullptr;
    while ((d = atomicPtr.load(std::memory_order_acquire)) == nullptr) {}
    // Safe to read d->value and d->extra
    std::cout << "  Published data: value=" << d->value
              << ", extra=" << d->extra << std::endl;
    delete d;
}

// ============================================================
// 5. Memory ordering comparison table
// ============================================================
void printOrderingTable() {
    std::cout << "\n--- Memory Ordering Summary ---" << std::endl;
    std::cout << "  Order             | Guarantee" << std::endl;
    std::cout << "  ------------------|-------------------------------------" << std::endl;
    std::cout << "  relaxed           | Atomicity only, no ordering" << std::endl;
    std::cout << "  acquire           | Reads after this see prior writes" << std::endl;
    std::cout << "  release           | Writes before this visible to acquire" << std::endl;
    std::cout << "  acq_rel           | Both acquire and release" << std::endl;
    std::cout << "  seq_cst (default) | Total global ordering" << std::endl;
}

int main() {
    std::cout << "=== Memory Model and Ordering ===" << std::endl;

    // Sequential consistency
    std::cout << "\n--- Sequential Consistency ---" << std::endl;
    std::thread sw1(seqWriter1);
    std::thread sw2(seqWriter2);
    std::thread sr(seqReader);
    sw1.join();
    sw2.join();
    sr.join();
    std::cout << "  seq_cst result: " << seqResult.load() << std::endl;

    // Acquire-Release
    std::cout << "\n--- Acquire-Release ---" << std::endl;
    std::thread prod(releaseProducer);
    std::thread cons(acquireConsumer);
    prod.join();
    cons.join();

    // Relaxed ordering for counters
    std::cout << "\n--- Relaxed Ordering ---" << std::endl;
    {
        std::thread t1(relaxedIncrement, 10000);
        std::thread t2(relaxedIncrement, 10000);
        t1.join();
        t2.join();
        std::cout << "  Relaxed counter: " << relaxedCounter.load() << std::endl;
    }

    // Publish-read pattern
    std::cout << "\n--- Publish/Read Pattern ---" << std::endl;
    std::thread pub(publishData);
    std::thread rd(readData);
    pub.join();
    rd.join();

    // Summary
    printOrderingTable();

    return 0;
}

// Expected output:
// === Memory Model and Ordering ===
//
// --- Sequential Consistency ---
//   seq_cst result: 42
//
// --- Acquire-Release ---
//   Acquire-Release consumer got: 99
//
// --- Relaxed Ordering ---
//   Relaxed counter: 20000
//
// --- Publish/Read Pattern ---
//   Published data: value=42, extra=100
//
// --- Memory Ordering Summary ---
//   Order             | Guarantee
//   ------------------|-------------------------------------
//   relaxed           | Atomicity only, no ordering
//   acquire           | Reads after this see prior writes
//   release           | Writes before this visible to acquire
//   acq_rel           | Both acquire and release
//   seq_cst (default) | Total global ordering
Run command
g++ -std=c++11 -Wall -Wextra -pthread -o example memory_model.cpp