An atomic operation on a shared variable is an indivisible sequence of instructions that appears to execute at a single instant to every other thread. No other thread can observe an intermediate state.
Formally, an atomic operation A on a memory location x is linearisable: there exists a linearisation point tA between invocation and response such that the effect of A on x occurs at tA.
Motivation
Why atomicity is needed
A lock implemented with ordinary loads and stores fails because the read of *lockvar and the subsequent write are separate instructions. Another thread may observe the same value in between and also enter the critical section.
The read-check-write sequence is not indivisible, so it cannot enforce mutual exclusion.
Hardware
Atomicity must be provided by the CPU. All modern CPUs expose atomic instructions in the ISA.
RISC-V AMO
The RISC-V atomic extension provides AMO (atomic memory operation) instructions.
amoswap.w.aq a2, a1, (a0)
This atomically swaps the word at (a0): a2 receives the old value and a1 is written to memory.
Patterns
Pattern
Effect
Example
Atomic swap
x←v; return old x
amoswap.w
Atomic fetch-and-add
x←x+v; return old x
amoadd.w
Test-and-set
if x=0 then x←1
amoswap.w
Compare-and-swap
if x=e then x←v
LR/SC]
C++
std::atomic<T>
std::atomic<int> lockvar{0};int old = lockvar.exchange(1, std::memory_order_acquire);lockvar.store(0, std::memory_order_release);
The compiler emits atomic instructions and the required ordering constraints.