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.
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
- 01Optimistic reads with a sequence counter
- 02When it wins
- 03Gotcha — readers retry; never for pointers
- 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.