Basics of atomic operations in C++

How to write lockless and data race free concurrent code

CMP
4 min readMar 29, 2024

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…

--

--

CMP
CMP

Written by CMP

Software engineer specializing in operating systems, navigating the intracicies of the C++ language and systems programming.

No responses yet