ReviseAlgo Logo

Concurrency

Atomic Operations

Write lock-free concurrent code using std::atomic and compare-and-swap (CAS) primitives.

Interview: Lock-free checks, CPU instruction level atomicity, and compare_exchange weak vs. strong.

Last Updated: June 13, 2026 9 min read

The std::atomic class template in <atomic> provides atomic read and write operations. This allows you to share data between threads safely without using heavyweight locks.

Atomic API

Performs basic arithmetic operations atomically, such as fetch_add() and fetch_sub().

Lock-Free

Check if operations map to raw CPU instructions instead of internal library locks using is_lock_free().

Compare-and-Swap

Compare and swap values atomically using compare_exchange_weak() and compare_exchange_strong().

compare_exchange_weak vs. compare_exchange_strong

These functions implement the Compare-and-Swap (CAS) primitive:

  • compare_exchange_weak: Can fail spuriously even if the current value matches the expected value. However, it compiles to faster instructions on architectures like ARM and is preferred inside loops.
  • compare_exchange_strong: Guarantees no spurious failures. It is preferred when CAS checks are performed once without a loop.

Code Walkthrough

A thread-safe lock-free counter and value-update loop using weak compare-and-swap.

#include <iostream>
#include <atomic>
#include <thread>
#include <vector>

std::atomic<int> g_counter(0);

void incrementCounter() { for (int i = 0; i < 1000; ++i) { g_counter.fetch_add(1, std::memory_order_seq_cst); // Thread-safe atomic increment } }

// Lock-free updates to a maximum value void updateMax(std::atomic<int>& maxVal, int val) { int current = maxVal.load(); while (val > current && !maxVal.compare_exchange_weak(current, val)) { // current is updated automatically on failure } }

int main() { std::vector<std::thread> threads; for (int i = 0; i < 5; ++i) threads.emplace_back(incrementCounter); for (auto& t : threads) t.join();

std::cout << "Final Counter: " << g_counter.load() << std::endl; return 0; }

Interview-Relevant Information

Q: What does it mean if std::atomic::is_lock_free() returns false?
Answer: It means the CPU does not support atomic instructions for that type size. The runtime falls back to using an internal mutex lock table to guarantee atomicity. This introduces locking overhead and can lead to priority inversion.

Q: Why is compare_exchange_weak preferred inside loops over compare_exchange_strong?
Answer: compare_exchange_weak can fail spuriously, but it compiles to simpler instructions on RISC processors (like ARM). Since loops retry anyway on failures, using the weak variant reduces execution overhead, making it faster than the strong variant.

Quick Checklist

Did you verify if your atomic types are lock-free? Do you use compare_exchange inside loops? If yes, your lock-free code is efficient.

Use Cases

Managing global transaction statistics or high-frequency analytics counters.

Implementing lock-free data structures (like single-producer single-consumer queues).

Common Mistakes

Assuming that multiple independent atomic operations are compound-atomic (e.g. read followed by write). You must use CAS for transactional updates.

Using complex structures with std::atomic that exceed CPU register sizes, resulting in fallback mutex locking.