multithreading · advanced

Seqlock — Optimistic Reads for Hot Data

A seqlock lets many readers access shared data with no locking and without ever blocking the writer, using a sequence counter that is incremented before a write (becoming odd = 'write in progress') and after it (even = 'stable'). A reader snapshots the counter, optimistically copies the data, reads the counter again, and retries if it was odd or changed — either condition meaning a write overlapped the read. It excels for a single writer and many readers of small, read-mostly data updated infrequently, such as a config snapshot, a clock/timestamp, or a stats block: in the common uncontended case a read is just two atomic loads and a copy, far cheaper than a mutex or even a shared atomic RMW because no cache line ownership is transferred, and readers never delay the writer, keeping write latency deterministic (the Linux kernel uses seqlocks for timekeeping). The constraints matter: the data must be trivially copyable because a reader can copy a half-written value (a torn read) that the retry then discards — harmless for a POD, but a torn read that ran a destructor or followed a dangling pointer could crash — and proper acquire/release fences are needed so data accesses do not reorder outside the counter checks. Heavy writes can starve readers with constant retries, so seqlocks are wrong for write-heavy or large data; use a mutex/reader-writer lock or RCU there.

🔑 Key line

A seqlock gives lock-free optimistic reads: bump an odd/even sequence counter around each write; readers copy the data and retry if the counter was odd (writing) or changed (overlapped). Best for one writer + many readers of small, trivially-copyable, read-mostly data — readers never block the writer; not for large or write-heavy data.

The code

std::atomic<unsigned> seq{0};
Snapshot data; // small, trivially-copyable
void write(const Snapshot& s) {
seq.fetch_add(1, std::memory_order_acquire); // -> ODD: write in progress
data = s;
seq.fetch_add(1, std::memory_order_release); // -> EVEN: done
}
Snapshot read() {
unsigned s1, s2;
Snapshot out;
do {
s1 = seq.load(acquire);
out = data; // optimistic copy
s2 = seq.load(acquire);
} while (s1 & 1 || s1 != s2); // retry if writing or changed
return out;
}

What this lesson walks through

  1. 01Optimistic reads with a sequence counter
  2. 02When it wins
  3. 03Gotcha — readers retry; never for pointers
  4. 04Constraints & dangers

A seqlock lets many readers read shared data with NO locking and without ever blocking the writer. A sequence counter is bumped before a write (making it odd = 'write in progress') and after (even = 'stable'). A reader snapshots the counter, copies the data, reads the counter again, and RETRIES if it was odd or changed — meaning a write overlapped its read.

See it animated — step by step, at your own pace

Unlock the full interactive walkthrough of Seqlock — Optimistic Reads for Hot Data and 100+ animated C++ interview lessons.

← Previous
Lock-Free SPSC Ring Buffer
Next →
Safe Reclamation: Hazard Pointers & RCU