0
0
CppHow-ToBeginner · 4 min read

How to Use std::atomic in C++ for Safe Concurrent Programming

Use std::atomic to declare variables that can be safely accessed and modified by multiple threads without locks. It provides atomic operations like load(), store(), and fetch_add() to prevent data races. Simply include <atomic> and declare your variable as std::atomic<Type>.
📐

Syntax

The std::atomic template wraps a variable to make its operations atomic and thread-safe. You declare it by specifying the type inside angle brackets.

  • std::atomic<int> counter; declares an atomic integer.
  • Use store() to set a value atomically.
  • Use load() to read the value atomically.
  • Atomic operations like fetch_add() modify the value safely.
cpp
std::atomic<int> counter(0);
counter.store(5); // set value atomically
int value = counter.load(); // read value atomically
int old = counter.fetch_add(1); // increment atomically, returns old value
💻

Example

This example shows two threads incrementing a shared atomic counter safely without locks. The final count matches the total increments.

cpp
#include <iostream>
#include <atomic>
#include <thread>

std::atomic<int> counter(0);

void increment(int times) {
    for (int i = 0; i < times; ++i) {
        counter.fetch_add(1, std::memory_order_relaxed);
    }
}

int main() {
    const int increments = 100000;
    std::thread t1(increment, increments);
    std::thread t2(increment, increments);

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

    std::cout << "Final counter value: " << counter.load() << std::endl;
    return 0;
}
Output
Final counter value: 200000
⚠️

Common Pitfalls

Common mistakes when using std::atomic include:

  • Using non-atomic types for shared data, causing data races.
  • Assuming atomic operations are always lock-free (depends on platform).
  • Ignoring memory order parameters, which can affect visibility between threads.
  • Using atomic pointers or complex types without proper care.

Always prefer std::atomic for shared variables accessed by multiple threads.

cpp
/* Wrong: Non-atomic shared variable causes data race */
int counter = 0;

void increment() {
    ++counter; // Not atomic, unsafe in threads
}

/* Right: Use std::atomic to avoid data race */
std::atomic<int> atomic_counter(0);

void safe_increment() {
    atomic_counter.fetch_add(1);
}
📊

Quick Reference

OperationDescriptionExample
DeclarationCreate atomic variablestd::atomic a(0);
storeSet value atomicallya.store(10);
loadGet value atomicallyint x = a.load();
fetch_addAdd and get old valueint old = a.fetch_add(1);
compare_exchangeCompare and swap valuea.compare_exchange_strong(expected, desired);

Key Takeaways

Use std::atomic to safely share variables between threads without locks.
Atomic operations like load(), store(), and fetch_add() prevent data races.
Always include and declare variables as std::atomic.
Be mindful of memory order for advanced synchronization needs.
Avoid using non-atomic types for shared data in multithreaded code.