Cpp Sequencer Pattern
Purpose
Generate unique, strictly increasing integers across threads without locks, used for Order IDs, trade IDs, message numbers, etc.
class Sequencer
{
static inline uint64_t next() noexcept
{
static std::atomic<uint64_t> counter{1};
return counter.fetch_add(1, std::memory_order_relaxed);
}
};
Core Principles
| Concept | Explanation |
|---|
static | Keeps one shared counter for all calls to next(). Lives until program exit. Guaranteed thread-safe. |
std::atomic<uint64_t> | Guarantees thread-safe read/write, no race conditions. |
fetch_add(1) | Atomically increments and returns the previous value. |
std::memory_order_relaxed | Avoids asynchronization overhead; ensures atomicity only. |
Why This Works
- Every call gets a unique integer (atomic increment guarantees no duplicates).
- Sequence is monotonic (always increasing).
- Execution is lock-free, so multiple threads can request IDs simultaneously.
- The cost is essentially one CPU instruction per call.
Execution Example
| Call # | Returned | Counter After |
|---|
| Thread A | 1 | 2 |
| Thread B | 2 | 3 |
| Thread C | 3 | 4 |
Memory Order
std::memory_order_relaxed
- Ensures atomic increment, but not ordering relative to other operations.
- Ideal when:
- You only need unique IDs
- IDs don’t depend on any shared memory state.
- Using stricter orders would add unnecessary synchronization cost.
Common Variants
- Custom Start Point
static inline uint64_t next(uint64_t start = 1) noexcept
{
static std::atomic<uint64_t> counter{start};
return counter.fetch_add(1, std::memory_order_relaxed);
}
- Resettable Sequencer
class ResettableSequencer
{
static inline std::atomic<uint64_t> counter{1};
public:
static uint64_t next() noexcept
{
return counter.fetch_add(1, std::memory_order_relaxed);
}
static void reset(uint64_t start = 1) noexcept
{
counter.store(start, std::memory_order_relaxed);
}
};
- Templated for Different Types
template<typename T = uint64_t>
class Sequencer
{
static inline T next() noexcept
{
static std::atomic<T> counter{1};
return counter.fetch_add(1, std::memory_order_relaxed);
}
};
Use Cases
| Use Case | Example |
|---|
| Order IDs | Each new order gets a unique integer ID. |
| Trade IDs | Each trade execution increments the sequence. |
| Message IDs | Logging or messaging systems that need unique reference numbers. |
| Request Correlation | Distributed services tagging requests with unique IDs. |
Design Summary
| Property | Value |
|---|
| Thread-safe | Yes |
| Lock-free | Yes |
| Unique IDs | Guaranteed |
| Monotonic | Guaranteed |
| Performance | Extremely fast as atomic increment only |
| Scope | Process-wide (global static) |