Atomic Support in C++
Learn about atomic support in C++ through the standard library's atomic variables, lock-free property, flags, and wait/notify operations.
We'll cover the following...
Atomic variables in the standard library
The standard library contains support for atomic variables, sometimes called atomics. An atomic variable is a variable that can safely be used and mutated from multiple threads without introducing data races.
Do you remember the data race example we looked at earlier where two threads updated a global counter? We solved it by adding a mutex lock together with the counter. Instead of using an explicit lock, we could have used a std::atomic<int>
instead:
std::atomic<int> counter;auto increment_counter(int n) {for (int i = 0; i < n; ++i)++counter; // Safe, counter is now an atomic<int}
The ++counter
is a convenient way of saying counter.fetch_add(1)
. All member functions that can be invoked on an atomic are safe to call from multiple threads concurrently.
The atomic types are from the <atomic>
header. There are typedefs for all the
scalar data types named on the std::atomic_int
form. This is identical to saying std::atomic<int>
. It is possible to wrap a custom type in a std::atomic
template, as long as the custom type is trivially copyable. Basically, this means that an object of a class is fully described by the bits of its data members. In that way, an object can be copied with, for example, std::memcpy()
by only copying the raw bytes. So, if a class contains virtual functions, pointers to dynamic memory, and so on, it’s no longer possible to just copy the raw bits of the object and expect it to work, and hence
it is not trivially copyable. This can be checked at compile time, so we will get a ...