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 two atomic operations that can be used with std::atomic
types, we explain what are atomic addition and subtraction 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 private types (i.e. int
, float
, double
, etc.) or 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<int> counter(0); // atomic type void myf() // a function to be used in multi-thread operations { counter++; // atomic operation } |
Atomic operations are operations on the of values 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, Let’s see some of these addition and subtract operations now.
What are atomic add and sub operations in C++?
Atomic types can be used in operations as in standard C++ types, For example we can do addition and subtract as below,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
std::atomic<int> counter(0); // atomic type void myf() // a function to be used in multi-thread operations { // atomic addition examples counter++; ++counter; counter+=5; // atomic subtract examples counter--; --counter; counter-=5; } |
What are fetch_add and fetch_sub operations in C++?
The fetch_add
is an atomic addition operation that atomically replaces the current value with the result of arithmetic addition of the given value.
The fetch_sub
is an atomic addition operation that atomically replaces the current value with the result of arithmetic subtraction of the given value.
Here the fetch term allows these operations to be done in read, modify and write orders. Here is an example
1 2 3 4 5 6 7 8 9 |
std::atomic<int> counter = 0; void myf() // a function to be used in multi-thread operations { counter.fetch_add(1); counter.fetch_sub(1); } |
When these operations are called, atomic_fetch_add and atomic_fetch_sub operations are called respectively. Note that, these operations can be used with memory orders (std::memory_order) too.
What are atomic_fetch_add and atomic_fetch_sub operations in modern C++?
The std::atomic_fetch_add
is an atomic addition operation that atomically adds given arguments to the value of pointed object parameter.
The std::atomic_fetch_sub
is an atomic addition operation that atomically subtracts given arguments from the value of pointed object parameter.
Here the fetch term allows these operations to be done in read, modify and write orders. Here is an example
1 2 3 4 5 6 7 8 9 |
std::atomic<int> counter = 0; void myf() // a function to be used in multi-thread operations { std::atomic_fetch_add( &counter, 1 ); std::atomic_fetch_sub( &counter, 1 ); } |
These operations are performed when the atomic_add and atomic_sub operations are called respectively. Note that, these operations can be used with memory orders (std::memory_order) too.
What are atomic add and sub operations with lambdas in C++?
We can use atomic operations with lambdas, for example we can create add and sub lambdas as below,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
std::atomic<int> counter(0); // atomic type int main() { std::vector< std::thread> myt; // vector for threads auto Ladd = [&] { ++counter; }; // lambda expression for an addition auto Lsub = [&] { --counter; }; // lambda expression for a subtraction myt.push_back( std::thread( Ladd ) ); // add lambdas to thread vector myt.push_back( std::thread( Lsub ) ); // add lambdas to thread vector for (auto& t : myt) t.join(); // wait each thread execution done and join back } |
Is there a full example of how to use atomic add and sub operations in C++?
Here is a full example that uses all types of add and sub operations with lambda and thread example too,
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
#include <iostream> #include <vector> #include <atomic> #include <thread> std::atomic<int> counter = 0; void myf() // a function to be used in multi-thread operations { // atomic addition examples counter++; ++counter; counter+=5; counter.fetch_add(1); std::atomic_fetch_add( &counter, 1); // atomic subtract examples counter--; --counter; counter-=5; counter.fetch_sub(1); std::atomic_fetch_sub( &counter, 1); } int main() { std::vector< std::thread> myt; // vector for threads auto Ladd = [&] { ++counter; }; // lambda expression for an addition auto Lsub = [&] { --counter; }; // lambda expression for a subtraction myt.push_back( std::thread( myf ) ); // add function to thread vector for (unsigned int i = 0; i < 16; i++) { myt.push_back( std::thread( Ladd ) ); // add lambdas to thread vector } for (unsigned int i = 0; i < 6; i++) { myt.push_back( std::thread( Lsub ) ); // add lambdas to thread vector } for (auto& t : myt) { t.join(); // wait each thread execution done and join back } std::cout << counter << std::endl; system("pause"); return 0; } |
Here the output result is 10; zero from myf(), 16 from add lambdas and -6 from sub lambdas.
Note that, every operation on these atomic types are called as atomic operation, there are logical operators too. For more information on this feature, see Atomic operations Proposal document.
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.