Cpp Sequencer Pattern

Purpose

Generate unique, strictly increasing integers across threads without locks, used for Order IDs, trade IDs, message numbers, etc.

General Form

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

ConceptExplanation
staticKeeps 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_relaxedAvoids 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 #ReturnedCounter After
Thread A12
Thread B23
Thread C34

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

  1. 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);
}
  1. 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);
	}
};
  1. 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 CaseExample
Order IDsEach new order gets a unique integer ID.
Trade IDsEach trade execution increments the sequence.
Message IDsLogging or messaging systems that need unique reference numbers.
Request CorrelationDistributed services tagging requests with unique IDs.

Design Summary

PropertyValue
Thread-safeYes
Lock-freeYes
Unique IDsGuaranteed
MonotonicGuaranteed
PerformanceExtremely fast as atomic increment only
ScopeProcess-wide (global static)