This is my first post, mainly just so that I have content on this board. I wrote about something that I recently read about.
A seqlock is useful when data is read often but rarely changed. The writer makes a counter odd, updates the data, then makes the counter even again. A reader will copy the data only when the counter has the same even value before and after the copy.
The problem is when the data gets copied. If a writer modifies data at the same time, the reader and writer are accessing ordinary memory concurrently, so the program has a data race. It does not matter that the reader checks the counter and discards the bad copy. It also does not matter whether Data is trivially copyable. The compiler assumes there are no data races during optimization, so the program is undefined in C++.
std::atomic<uint64_t> sequence{0};
Data data;
Data read() {
while (true) {
auto before = sequence.load(std::memory_order_seq_cst);
if (before & 1)
continue;
Data copy = data; // ordinary unprotected access
auto after = sequence.load(std::memory_order_seq_cst);
if (before == after)
return copy;
}
}
A portable implementation must remove the conflicting non-atomic access. Using std::atomic<Data> is simple, but it may fall back to an internal lock when Data is not lock-free. Another option is to atomically publish a pointer to an immutable snapshot, which replaces the data race with allocation and memory-reclamation costs. Kernel implementations rely on operations such as READ_ONCE, WRITE_ONCE and explicit compiler/CPU barriers to keep memory accesses ordered around the sequence checks.