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
| Operation | Description | Example |
|---|---|---|
| Declaration | Create atomic variable | std::atomic |
| store | Set value atomically | a.store(10); |
| load | Get value atomically | int x = a.load(); |
| fetch_add | Add and get old value | int old = a.fetch_add(1); |
| compare_exchange | Compare and swap value | a.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.