The concurrency support library in modern C++ is designed to solve problems in multi-thread operations. Since the C++11 standard, this library includes built-in support for threads (std::thread
) with atomic operations (std::atomic
). In this post, we explain what two atomic operations that can be used with std::atomic
types, we explain what are atomic load and store operations.
Table of Contents
What is atomic (std::atomic) in C++?
C++11 adds atomic types and operations to the standard. Atomic types and operations provide a way of writing multi-threaded applications without using locks. In modern C++, the std::atomic<>
template class is defined in the <atomic>
header and it can be used to wrap other types to do atomic operations on that type. When a thread writes to an atomic object another thread can read from it. Every instantiation and full specialization of the std::atomic
template defines an atomic type.
Atomic types ensure any read or write operation synchronizes as part of multi-thread operations, (i.e. using these types in std::thread
). They work well on any type that is trivially copyable types which means it has at least one eligible copy constructor, move constructor, copy assignment operator, or move assignment operator and has non-deleted trivial destructor.
Here is a simple syntax for the atomic declaration:
1 2 3 |
atomic< type > type_name; |
Here is a simple std::atomic
example:
1 2 3 4 5 6 7 8 |
std::atomic<unsigned int> counter(0); // atomic type void myf() // a function to be used in multi-thread operations { counter++; // atomic operation } |
Atomic operations are operations one of the atomic types (std::atomic
objects) in the atomic library that allows lockless concurrent programming. These operations are good in data races and these objects are free of data races. Different atomic operations can be done on the same atomic object in their sync.
std::atomic
has many features to be used in atomic operations, i.e. load, store, operator=, wait, exchange, is_lock_free, etc. Let’s see these load and store operations now.
What are atomic load and store operations in C++?
Atomic load and store operations are load
, atomic_load
, atomic_load_explicit
, store
, atomic_store
, atomic_store_explicit
. Now let’s see how we can use these atomic operations in multi-threading operations.
How to use atomic load in C++?
The atomic load is an atomic operation that returns the value contained in that atomic object. Here is the syntax,
1 2 3 |
T load( std::memory_order order = std::memory_order_seq_cst ) const noexcept; |
In example, you can obtain the value pointed by object with std::atomic::load().
1 2 3 4 5 6 7 8 9 |
std::atomic<int> x = 0; void myf() // a function to be used in multi-thread operations { int y = x.load(); // atomic load operation x++; } |
or we can use with memory order,
1 2 3 4 5 6 7 8 9 |
std::atomic<int> x = 0; void myf() // a function to be used in multi-thread operations { int y = x.load(std::memory_order_acquire); // atomic load operation x++; } |
Using load with memory order specifies how memory accesses are to be ordered in atomic load operation. This memory order types are defined in atomic library, here are permitted memory orders for atomic load,
- memory_order_relaxed
- memory_order_consume
- memory_order_acquire
- memory_order_seq_cst
How to use std::atomic_load in modern C++?
The atomic_load
is an atomic operation that returns the value contained in that atomic object. Here is the syntax,
1 2 3 |
template< class T > T atomic_load( const std::atomic<T>* obj ) noexcept; |
and here is the simplified example,
1 2 3 4 5 6 7 8 |
std::atomic<int*> ptr = new int; void load_x() { int *p = std::atomic_load(&ptr); } |
How to use std::atomic_load_explicit in modern C++?
We can use atomic_load_explicit
with memory order. Here are the permitted memory synchronization orders for std::atomic_load_explicit
:
- std::memory_order_relaxed
- std::memory_order_consume
- std::memory_order_acquire
- std::memory_order_seq_cst
How to use atomic store in modern C++?
The atomic store
is an atomic operation that atomically replaces the current value of atomic object with a non atomic value. Here is the general syntax for it,
1 2 3 |
void store( T desired, std::memory_order order = std::memory_order_seq_cst ) noexcept; |
here is an example to using std::store with an object parameter,
1 2 3 4 5 6 7 8 |
std::atomic<int> x = 0; void store_x(int a) { x.store(a); } |
here is an example to using std::store with object and memory order parameters,
1 2 3 4 5 6 7 8 |
std::atomic<int> x = 0; void store_x(int a) { x.store(a, std::memory_order_acquire); } |
We can use store
with memory order. Here are the permitted memory synchronization orders for atomic store:
- std::memory_order_relaxed
- std::memory_order_release
- std::memory_order_seq
How to use atomic_store in modern C++
The atomic_store
is an atomic operation that replaces the value to the atomic object. Here is the syntax,
1 2 3 |
template< class T > void atomic_store( std::atomic<T>* obj, typename std::atomic<T>::value_type value ) noexcept; |
and here is the simplified example,
1 2 3 4 5 6 7 8 9 |
std::atomic<int> x = 0; std::atomic<int> y = 20; void store_x() { std::atomic_store(&x, y); } |
How to use atomic_store_explicit in modern C++?
We can use atomic_store_explicit
with memory order. Here are the permitted memory synchronization orders are for std::atomic_store_explicit
are:
- std::memory_order_relaxed
- std::memory_order_release
- std::memory_order_seq
In addition to these load and store templates, there are std::atomic_load_explicit
and std::atomic_store_explicit
. Note that, there are other atomic operations in atomic library. Every operation on these atomic types are called as atomic operation. For more information on this feature, see Atomic operations Proposal document.
Is there a full example of load and store atomic operations in C++?
Here is a full example about different types of load and store atomic operations in modern C++,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
#include <iostream> #include <atomic> std::atomic<int> x = 0; std::atomic<int> y = 20; std::atomic<int*> ptr = new int; void load_x() { int a = x.load(); int b = x.load(std::memory_order_relaxed); int *p = std::atomic_load(&ptr); int *q = std::atomic_load_explicit(&ptr, std::memory_order_seq_cst); } void store_x(int a) { x.store(a); x.store(a, std::memory_order_release); std::atomic_store(&x, std::memory_order_relaxed); std::atomic_store_explicit(&x, a, std::memory_order_seq); } int main() { } |
C++ Builder is the easiest and fastest C and C++ IDE for building simple or professional applications on the Windows, MacOS, iOS & Android operating systems. It is also easy for beginners to learn with its wide range of samples, tutorials, help files, and LSP support for code. RAD Studio’s C++ Builder version comes with the award-winning VCL framework for high-performance native Windows apps and the powerful FireMonkey (FMX) framework for cross-platform UIs.
There is a free C++ Builder Community Edition for students, beginners, and startups; it can be downloaded from here. For professional developers, there are Professional, Architect, or Enterprise version.