short

The syscall behind C++ asymmetric fences

· english· audience: working-cpp· AI-generated, reviewed by Filip Sajdak

A sequentially consistent fence is expensive, and plenty of concurrent algorithms need one on a path that runs constantly. Asymmetric fences split that cost in two. The common path gets a light fence: a compiler barrier only, std::atomic_signal_fence, which emits no hardware instruction at all. The rare path gets a heavy fence that does the real work for both sides.

On Linux the heavy fence is one syscall: membarrier(). It sends an inter-processor interrupt to every other thread in the process and makes each one execute a full memory barrier before it returns. You have moved the barrier off your hot path and onto the thread that only occasionally needs ordering. Folly uses exactly this in its hazard pointers, RCU, and thread-pool executors. C++ blessed the pattern in P1202 and wrote it into [atomics.order]/4.

Here it is running. A store-buffer (Dekker) test where the outcome a == 0 && b == 0 is forbidden under sequential consistency. One thread uses the light fence, the other the heavy membarrier(); across 50,000 trials the forbidden result never appears. Compiler Explorer’s executors run Linux, so this actually executes:

Down through the kernel to a possible defect

That the pattern works is the easy part. Ryan Chung Yi Sheng’s deep-dive does not stop there. It follows the idea all the way down: how the kernel’s IPI-plus-barrier dance actually establishes the ordering (including the awkward case where a thread is descheduled mid-barrier), and why the obvious way to specify asymmetric fences in C++ failed.

The naive formalization, a plain synchronizes-with between each light and heavy pair, chains transitively and ends up forbidding outcomes real hardware allows, which makes it unimplementable. The accepted wording instead adds ordering to the single total order over seq_cst operations, sidestepping the bad transitivity. And then the post goes one step further than the standard: a formal Linux-kernel-memory-model argument that the context-switch case may not provide the “strongly happens-before” guarantee C++ now claims for it. A possible latent defect, surfaced by reading the two models against each other.


Source: Ryan Chung Yi Sheng, “membarrier() and Asymmetric Fences”. Background: P1202 “Asymmetric Fences”, the Linux membarrier(2) man page.