C++ Atomic | A complete guide to Memory Order

C++ Atomic | A complete guide to Memory Order

이 글의 핵심

A comprehensive guide to C++ atomic operations and memory ordering: relaxed, acquire, release, seq_cst, and practical synchronization patterns.

atomic basic```cpp

#include #include using namespace std;

atomic counter(0);

void increment() { for (int i = 0; i < 1000; i++) { counter++; // Atomic operations } }

int main() { thread t1(increment); thread t2(increment);

t1.join();
t2.join();

cout << counter << endl; // 2000 (always correct) }


## atomic vs mutex

```cpp
// use mutex
mutex mtx;
int counter = 0;

void incrementMutex() {
    for (int i = 0; i < 1000; i++) {
        lock_guard<mutex> lock(mtx);
        counter++;
    }
}

// use atomic (faster)
atomic<int> atomicCounter(0);

void incrementAtomic() {
    for (int i = 0; i < 1000; i++) {
        atomicCounter++;
    }
}
```## Memory Order

### memory_order_relaxed```cpp
atomic<int> x(0);
atomic<int> y(0);

// thread 1
void thread1() {
    x.store(1, memory_order_relaxed);
    y.store(1, memory_order_relaxed);
}

// thread 2
void thread2() {
    while (y.load(memory_order_relaxed) == 0);
    // There is no guarantee that x is 1!
    cout << x.load(memory_order_relaxed) << endl;
}

memory_order_acquire/release

atomic<int> data(0);
atomic<bool> ready(false);

// producer
void producer() {
    data.store(42, memory_order_relaxed);
    ready.store(true, memory_order_release);  // release
}

// consumer
void consumer() {
    while (!ready.load(memory_order_acquire));  // acquire
    cout << data.load(memory_order_relaxed) << endl;  // 42 guaranteed
}
```### memory_order_seq_cst (default)```cpp
atomic<int> x(0);
atomic<int> y(0);

// Ensure sequential consistency
x.store(1, memory_order_seq_cst);
y.store(1, memory_order_seq_cst);

// All threads run in the same order
```## Practical example

### Example 1: Spinlock```cpp
class SpinLock {
private:
    atomic_flag flag = ATOMIC_FLAG_INIT;
    
public:
    void lock() {
        while (flag.test_and_set(memory_order_acquire)) {
            // spin (wait)
        }
    }
    
    void unlock() {
        flag.clear(memory_order_release);
    }
};

int main() {
    SpinLock spinlock;
    int counter = 0;
    
    auto increment = [&]() {
        for (int i = 0; i < 1000; i++) {
            spinlock.lock();
            counter++;
            spinlock.unlock();
        }
    };
    
    thread t1(increment);
    thread t2(increment);
    
    t1.join();
    t2.join();
    
    cout << counter << endl;  // 2000
}
```### Example 2: Lock-Free Stack```cpp
template<typename T>
class LockFreeStack {
private:
    struct Node {
        T data;
        Node* next;
        Node(T d) : data(d), next(nullptr) {}
    };
    
    atomic<Node*> head;
    
public:
    LockFreeStack() : head(nullptr) {}
    
    void push(T value) {
        Node* newNode = new Node(value);
        newNode->next = head.load(memory_order_relaxed);
        
        while (!head.compare_exchange_weak(
            newNode->next, newNode,
            memory_order_release,
            memory_order_relaxed
        ));
    }
    
    bool pop(T& result) {
        Node* oldHead = head.load(memory_order_relaxed);
        
        while (oldHead && !head.compare_exchange_weak(
            oldHead, oldHead->next,
            memory_order_acquire,
            memory_order_relaxed
        ));
        
        if (oldHead) {
            result = oldHead->data;
            delete oldHead;
            return true;
        }
        
        return false;
    }
};

int main() {
    LockFreeStack<int> stack;
    
    thread t1([&]() {
        for (int i = 0; i < 100; i++) {
            stack.push(i);
        }
    });
    
    thread t2([&]() {
        int value;
        for (int i = 0; i < 50; i++) {
            if (stack.pop(value)) {
                cout << value << " ";
            }
        }
    });
    
    t1.join();
    t2.join();
}
```### Example 3: Double check locking```cpp
class Singleton {
private:
    static atomic<Singleton*> instance;
    static mutex mtx;
    
    Singleton() {}
    
public:
    static Singleton* getInstance() {
        Singleton* tmp = instance.load(memory_order_acquire);
        
        if (tmp == nullptr) {
            lock_guard<mutex> lock(mtx);
            tmp = instance.load(memory_order_relaxed);
            
            if (tmp == nullptr) {
                tmp = new Singleton();
                instance.store(tmp, memory_order_release);
            }
        }
        
        return tmp;
    }
};

atomic<Singleton*> Singleton::instance(nullptr);
mutex Singleton::mtx;

compare_exchange

atomic<int> value(0);

int expected = 0;
int desired = 10;

// Weak version (possible spurious failure)
if (value.compare_exchange_weak(expected, desired)) {
cout << "success" << endl;
} else {
cout << "Failed, current value: " << expected << endl;
}

// Strong version (no spurious failure)
if (value.compare_exchange_strong(expected, desired)) {
cout << "success" << endl;
}
```## Organize memory order

| order | Description | Usage Scenarios |
|------|------|-------------|
| relaxed | No order guarantee | counter |
| acquire | Prevent future read/write relocations | Acquire Rock |
| release | Prevent previous read/write relocation | unlock |
| acq_rel | acquire + release | RMW operation |
| seq_cst | sequential consistency (default) | When in doubt |

## Frequently occurring problems

### Issue 1: Incorrect memory order```cpp
// ❌ Data Race
atomic<bool> ready(false);
int data = 0;

void producer() {
    data = 42;
ready.store(true, memory_order_relaxed);  // Wrong!
}

void consumer() {
    while (!ready.load(memory_order_relaxed));
cout << data << endl;  // May not be 42!
}

// ✅ Correct order
void producer() {
    data = 42;
    ready.store(true, memory_order_release);
}

void consumer() {
    while (!ready.load(memory_order_acquire));
cout << data << endl;  // 42 guaranteed
}
```### Issue 2: ABA Issues```cpp
// ❌ ABA Problems
atomic<Node*> head;

void pop() {
    Node* oldHead = head.load();
    // Here, other threads can pop and push.
    head.compare_exchange_strong(oldHead, oldHead->next);
    // oldHead may be a different node!
}

// ✅ Use tag pointers
struct TaggedPointer {
    Node* ptr;
    uintptr_t tag;
};

atomic<TaggedPointer> head;
```### Problem 3: Incorrect use of atomic```cpp
// ❌ Non-atomic type
struct Big {
    int data[100];
};

atomic<Big> a;  // Compilation error or lock based

// ✅ Small types only
atomic<int> a;
atomic<bool> b;
atomic<void*> c;
```## Performance considerations```cpp
// relaxed (fastest)
counter.fetch_add(1, memory_order_relaxed);

// acquire/release (medium)
flag.store(true, memory_order_release);

// seq_cst (slowest, default)
counter.fetch_add(1, memory_order_seq_cst);
```## FAQ

### Q1: When do you use atomic?

**A**: 
- Simple counter
- flag
- Lock-free data structure

### Q2: atomic vs mutex?

**A**: 
- **atomic**: simple operation, fast
- **mutex**: complex operation, multiple variables

### Q3: How do I select memory_order?

**A**: 
- seq_cst (default) if in doubt
- acquire/release if performance is important
- Simple counter is relaxed

### Q4: Is lock-free always faster?

**A**: No. If there is a lot of contention, a mutex may be faster.

### Q5: What is atomic debugging?

**A**: 
- Use ThreadSanitizer
- Added logging
- Test starting from simple cases

### Q6: What are atomic learning resources?

**A**: 
- "C++ Concurrency in Action" (Anthony Williams)
- cppreference.com
- Preshing on Programming Blog

---

## Good article to read together (internal link)

Here's another article related to this topic.

- [C++ Memory Order | “Memory Order” Guide](/blog/cpp-memory-order/)
- [C++ Atomic Operations | “Atomic Operations” Guide](/blog/cpp-atomic-operations/)
- [C++ Lock-Free Programming | "Lock Free Programming" Guide](/blog/cpp-lock-free-programming/)

## Practical tips

These are tips that can be applied right away in practice.

### Debugging tips
- If you run into a problem, check the compiler warnings first.
- Reproduce the problem with a simple test case

### Performance Tips
- Don't optimize without profiling
- Set measurable indicators first

### Code review tips
- Check in advance for areas that are frequently pointed out in code reviews.
- Follow your team's coding conventions

---
## Practical checklist

This is what you need to check when applying this concept in practice.

### Before writing code
- [ ] Is this technique the best way to solve the current problem?
- [ ] Can team members understand and maintain this code?
- [ ] Does it meet the performance requirements?

### Writing code
- [ ] Have you resolved all compiler warnings?
- [ ] Have you considered edge cases?
- [ ] Is error handling appropriate?

### When reviewing code
- [ ] Is the intent of the code clear?
- [ ] Are there enough test cases?
- [ ] Is it documented?

Use this checklist to reduce mistakes and improve code quality.

---

## Keywords covered in this article (related search terms)

This article will be helpful if you search for C++, atomic, memory-order, concurrency, lock-free, etc.

---

## Related articles

- [C++ Atomic Operations | ](/blog/cpp-atomic-operations/)
- [C++ Lock-Free Programming | ](/blog/cpp-lock-free-programming/)
- [C++ Memory Order | ](/blog/cpp-memory-order/)
- [C++ Lock-Free Programming Practice | CAS, ABA, memory order, high-performance queue [#34-3]](/blog/cpp-series-34-3-lock-free/)
- [C++ Lock-Free Programming Practice | CAS, ABA, memory order, high-performance queue [#51-5]](/blog/cpp-series-51-5-lock-free-programming/)