Basics of atomic operations in C++
Introduction
In C++11, the <atomic>
header was added to the C++ standard, allowing programmers to perform so-called “atomic” operations. Like an atom in the context of chemistry, atomic operations are “indivisible” — meaning the operation will either appear to have been completed entirely or not happen at all, with no intermediate state visible to others. This article is a gentle introduction to the basic core concepts of atomic operations in C++ without diving deep into the more intermediate-advanced topics.
Motivation
From a programming perspective, atomic operations are incredibly useful for concurrency. When a variable is shared by multiple threads, standard non-atomic operations can result in data races. For example, suppose we have a shared variable and two concurrent threads that try to increment it:
#include <thread>
int counter = 0;
void incrementCounter() {
counter++;
}
int main() {
std::jthread thread1(incrementCounter);
std::jthread thread2(incrementCounter);
return 0;
}
At first glance, the expected result of counter
is 2. However, it is not guaranteed because the result depends on the timing of the threads. It is entirely possible that thread1
reads the value of counter
to be 0 and goes to increment it, but before the operation completes, thread2
also reads the value of counter
as 0 and increments it. The…