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.
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.