C++ Atomics, Memory Ordering, and Safe Publication
Company: Millennium
Role: Software Engineer
Category: Software Engineering Fundamentals
Difficulty: medium
Interview Round: Onsite
What is an atomic operation in C++, and how is `std::atomic` different from protecting shared state with a mutex?
Use a shared counter and a producer publishing a payload to a consumer to explain the difference between atomic access and ordering other memory accesses. Explain why a sequence of atomic operations is not automatically one indivisible transaction.
### Constraints & Assumptions
- Use the C++ memory model and `std::atomic`; do not assume an atomic type is always lock-free.
- The counter is used only for counting, with no associated payload that its value publishes.
- In the publication example, one producer initializes a payload once and signals one consumer with an atomic flag. The producer does not modify that payload afterward.
- A hypothetical transfer between two balances requires their combined invariant to be observed consistently.
### Clarifying Questions to Ask
- Does this atomic variable only count events, or does it signal that other data is ready?
- Are there multiple writers, and can the payload be reused or changed after publication?
- Is the requirement atomicity of one field or a transaction across several fields?
```hint Follow the publication edge
An atomic ready flag is useful only if observing it also establishes the required ordering for the ordinary payload accesses.
```
### What a Strong Answer Covers
- Indivisible operations on an atomic object and the absence of data races on that object when it is accessed correctly.
- An atomic read-modify-write for incrementing a counter, rather than a separate load and store.
- The distinction among relaxed, acquire/release, and sequentially consistent ordering.
- A correct one-time payload-publication example and why a relaxed flag is insufficient for that example.
- Why `volatile`, several atomic fields, and “lock-free” are not substitutes for the needed synchronization contract.
### Follow-up Questions
- Why can `counter.store(counter.load() + 1)` lose increments even though both operations are atomic?
- What would need to change if the producer repeatedly overwrote the published payload?
Overview: Understand C++ atomics, relaxed counters, acquire-release publication, lost updates, and when multi-field invariants still require a mutex.
What is an atomic operation in C++, and how is std::atomic different from protecting shared state with a mutex?
Use a shared counter and a producer publishing a payload to a consumer to explain the difference between atomic access and ordering other memory accesses. Explain why a sequence of atomic operations is not automatically one indivisible transaction.
Constraints & Assumptions
Use the C++ memory model and
std::atomic
; do not assume an atomic type is always lock-free.
The counter is used only for counting, with no associated payload that its value publishes.
In the publication example, one producer initializes a payload once and signals one consumer with an atomic flag. The producer does not modify that payload afterward.
A hypothetical transfer between two balances requires their combined invariant to be observed consistently.
Clarifying Questions to Ask Guidance
Does this atomic variable only count events, or does it signal that other data is ready?
Are there multiple writers, and can the payload be reused or changed after publication?
Is the requirement atomicity of one field or a transaction across several fields?
What a Strong Answer Covers Guidance
Indivisible operations on an atomic object and the absence of data races on that object when it is accessed correctly.
An atomic read-modify-write for incrementing a counter, rather than a separate load and store.
The distinction among relaxed, acquire/release, and sequentially consistent ordering.
A correct one-time payload-publication example and why a relaxed flag is insufficient for that example.
Why
volatile
, several atomic fields, and “lock-free” are not substitutes for the needed synchronization contract.
Follow-up Questions Guidance
Why can
counter.store(counter.load() + 1)
lose increments even though both operations are atomic?
What would need to change if the producer repeatedly overwrote the published payload?