Traditionally, C and C++ have a long tradition of portable code for system programming. The atomic feature that was introduced in the C++11 standard reinforces this by adding, natively, the guarantee that an operation is seen as atomic by other threads. Atomic is a template, such as template <class T> struct atomic; or template <class T> struct atomic<T*>;. C++20 has added shared_ptr and weak_ptr to T and T*. Any operation that's performed on the atomic variable is now protected from other threads.
Learning how atomic works
How to do it...
std::atomic is an important aspect of modern C++ for dealing with concurrency. Let's write some code to master the concept:
- The first snippet of code shows the basics of atomic operations. Let's write this now:
std::atomic<int> speed (0); // Other threads have access to the speed variable
auto currentSpeed = speed.load(); // default memory order: memory_order_seq_cst
- In this second program, we can see that the is_lock_free() method returns true if the implementation is lock-free or if it has been implemented using a lock. Let's write this code:
#include <iostream>
#include <utility>
#include <atomic>
struct MyArray { int z[50]; };
struct MyStr { int a, b; };
int main()
{
std::atomic<MyArray> myArray;
std::atomic<MyStr> myStr;
std::cout << std::boolalpha
<< "std::atomic<myArray> is lock free? "
<< std::atomic_is_lock_free(&myArray) << std::endl
<< "std::atomic<myStr> is lock free? "
<< std::atomic_is_lock_free(&myStr) << std::endl;
}
- Let's compile the program. When doing so, you may need to add the atomic library to g++ (due to a GCC bug) with g++ atomic.cpp -latomic.
How it works...
std::atomic<int> speed (0); defines a speed variable as an atomic integer. Although the variable will be atomic, this initialization is not atomic! Instead, the following code: speed +=10; atomically increases the speed of 10. This means that there will not be race conditions. By definition, a race condition happens when among the threads accessing a variable, at least 1 is a writer.
The std::cout << "current speed is: " << speed; instruction reads the current value of the speed automatically. Pay attention to the fact that reading the value from speed is atomic but what happens next is not atomic (that is, printing it through cout). The rule is that read and write are atomic but the surrounding operations are not, as we've seen.
The output of the second program is as follows:

The basic operations for atomic are load, store, swap, and cas (short for compare and swap), which are available on all types of atomics. Others are available, depending on the types (for example, fetch_add).
One question remains open, though. How come myArray uses locks and myStr is lock-free? The reason is simple: C++ provides a lock-free implementation for all the primitive types, and the variables inside MyStr are primitive types. A user will set myStr.a and myStr.b. MyArray, on the other hand, is not a fundamental type, so the underlying implementation will use locks.
The standard guarantee is that for each atomic operation, every thread will make progress. One important aspect to keep in mind is that the compiler makes code optimizations quite often. The use of atomics imposes restrictions on the compiler regarding how the code can be reordered. An example of a restriction is that no code that preceded the write of an atomic variable can be moved after the atomic write.
There's more...
In this recipe, we've used the default memory model called memory_order_seq_cst. Some other memory models that are available are:
- memory_order_relaxed: Only the current operation atomicity is guaranteed. That is, there are no guarantees on how memory accesses in different threads are ordered with respect to the atomic operation.
- memory_order_consume: The operation is ordered to happen once all accesses to memory in the releasing thread that carry a dependency on the releasing operation have happened.
- memory_order_acquire: The operation is ordered to happen once all accesses to memory in the releasing thread have happened.
- memory_order_release: The operation is ordered to happen before a consume or acquire operation.
- memory_order_seq_cst: The operation is sequentially consistent ordered.
See also
The books Effective Modern C++ by Scott Meyers and The C++ Programming Language by Bjarne Stroustrup cover these topics in great detail. Furthermore, the Atomic Weapons talk from Herb Sutter, freely available on YouTube (https://www.youtube.com/watch?v=A8eCGOqgvH4), is a great introduction.