Atomically increment two integers with CAS
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
If two integers must be incremented as one indivisible operation, updating two separate atomic variables is not enough. Another thread can still observe an intermediate state between the two increments. To make the pair update atomic with compare-and-swap, you need one shared state value that represents both integers together, or you need a lock.
Why Two Independent Atomics Are Not One Atomic Operation
Consider two atomics:
If one thread does:
another thread can observe:
- '
aupdated andbold' - '
aold andbupdated' - both updated
Each increment is atomic by itself, but the pair is not atomic as a unit. That is the core problem.
Pack Both Integers into One CAS Target
One common lock-free approach is to store both integers in one machine word and use CAS on that combined value.
Here the CAS operates on one uint64_t, so the pair transition from old to new is atomic as long as the platform supports atomic operations for that width.
CAS Works Best When the State Fits in One Atomic Object
The combined-state approach is the core CAS idea:
- read the whole state
- compute the next whole state
- CAS old state to new state
- retry if another thread won the race
This pattern is good when both values fit comfortably into one atomic slot. If the state is larger, you either need platform-specific wide CAS support or a different synchronization strategy.
Sometimes a Lock Is the Better Answer
If the only requirement is correctness, a mutex is often simpler and clearer.
A lock is not automatically a bad solution. CAS-based code is harder to write, harder to test, and easier to get wrong. Use CAS when you have a real reason to avoid locks, not because lock-free sounds better in theory.
Watch Out for Overflow and ABA-Like Design Issues
Packing integers into one word introduces practical constraints:
- each field must fit the chosen bit width
- overflow behavior must be defined
- readers and writers must agree on the packing layout
The CAS loop can also spin heavily under contention. That is another reason to compare the lock-free version against a simple mutex before declaring the CAS solution better.
Common Pitfalls
- Assuming two separate atomic increments automatically form one atomic pair update.
- Trying to CAS two memory locations independently and calling the combined effect atomic.
- Using a packed representation without checking integer width and overflow behavior.
- Choosing CAS for simplicity when a mutex would be easier to maintain and verify.
- Ignoring contention costs in a retry loop that may spin frequently under load.
Summary
- Two integers can be incremented atomically with CAS only if both live inside one atomic state value.
- Packing both integers into one
uint64_tis a common lock-free technique. - The CAS loop reads the whole state, computes the next state, and retries on conflict.
- A mutex is often the simpler and more maintainable solution when raw lock-free behavior is not required.
- Correctness comes from atomicity of the combined state, not from using CAS on two separate variables.

