atomic¶
Overview¶
prototype::atomic<T> provides lock-free atomic operations on scalar types. It guarantees indivisible read-modify-write operations without mutex overhead.
When to Use¶
- Shared counters (statistics, reference counting)
- Flag variables (shutdown signals, ready flags)
- Lock-free algorithms (compare-and-swap loops)
API Reference¶
namespace prototype {
enum memory_order {
memory_order_relaxed,
memory_order_acquire,
memory_order_release,
memory_order_acq_rel,
memory_order_seq_cst
};
template <typename T>
class atomic {
public:
atomic();
explicit atomic(T desired);
// Not copyable or movable
atomic(const atomic&) = delete;
atomic& operator=(const atomic&) = delete;
// Load/Store
T load(memory_order order = memory_order_seq_cst) const;
void store(T desired, memory_order order = memory_order_seq_cst);
// Exchange
T exchange(T desired, memory_order order = memory_order_seq_cst);
bool compare_exchange_weak(T& expected, T desired,
memory_order success = memory_order_seq_cst,
memory_order failure = memory_order_seq_cst);
bool compare_exchange_strong(T& expected, T desired,
memory_order success = memory_order_seq_cst,
memory_order failure = memory_order_seq_cst);
// Arithmetic (integer types only)
T fetch_add(T arg, memory_order order = memory_order_seq_cst);
T fetch_sub(T arg, memory_order order = memory_order_seq_cst);
T fetch_and(T arg, memory_order order = memory_order_seq_cst);
T fetch_or(T arg, memory_order order = memory_order_seq_cst);
T fetch_xor(T arg, memory_order order = memory_order_seq_cst);
// Operators
T operator++();
T operator--();
T operator+=(T arg);
T operator-=(T arg);
};
// Memory fences
void atomic_thread_fence(memory_order order);
} // namespace prototype
Example Code¶
#include <prototype/concurrency/atomic.hpp>
// Lock-free reference counting
prototype::atomic<int> ref_count{1};
void acquire() {
ref_count.fetch_add(1, prototype::memory_order_relaxed);
}
void release() {
if (ref_count.fetch_sub(1, prototype::memory_order_acq_rel) == 1) {
destroy();
}
}
// Compare-and-swap loop (lock-free stack push)
prototype::atomic<Node*> head{nullptr};
void push(Node* node) {
node->next = head.load(prototype::memory_order_relaxed);
while (!head.compare_exchange_weak(node->next, node,
prototype::memory_order_release,
prototype::memory_order_relaxed)) {
// Retry on failure
}
}
Memory Ordering Guide¶
| Order | Guarantee | Cost | Use Case |
|---|---|---|---|
relaxed |
Atomicity only | Lowest | Counters, statistics |
acquire |
Reads after this see writes before matching release | Low | Load side of publish |
release |
Writes before this are visible after matching acquire | Low | Store side of publish |
acq_rel |
Both acquire and release | Medium | Read-modify-write |
seq_cst |
Total global order | Highest | Default, simple correctness |
Performance Characteristics¶
| Operation | x86 Cost | ARM Cost |
|---|---|---|
load(relaxed) |
Free (plain load) | Free |
load(acquire) |
Free (x86 TSO) | ldar instruction |
store(release) |
Free (x86 TSO) | stlr instruction |
store(seq_cst) |
mfence or lock xchg |
stlr + barrier |
fetch_add |
lock xadd |
ldaxr/stlxr loop |
compare_exchange |
lock cmpxchg |
ldaxr/stlxr loop |
Safety Notes¶
ABA Problem
compare_exchange is susceptible to the ABA problem in pointer-based lock-free structures. Consider tagged pointers or hazard pointers for production use.
Default to seq_cst
Use memory_order_seq_cst (the default) unless you have measured a performance bottleneck and understand the memory model implications of weaker orderings.
Design Notes¶
- Implemented using compiler intrinsics (
__atomic_*GCC/Clang,_Interlocked*MSVC) - Supported types: integers, pointers, and any trivially-copyable type ≤ 8 bytes
- No locks, no kernel transitions
sizeof(atomic<int>)=sizeof(int)(no overhead)