RCU (Read-Copy-Update)
In plain English
Plain definition
A lock-free technique where readers see either an old or new version of a data structure without ever blocking, and the old version is freed only after every reader that might still hold a reference to it has finished.
RCU works on a copy-on-write principle combined with deferred garbage collection. A writer never mutates a live structure in place; it allocates a new version, publishes it atomically via a single pointer swap (typically with a release-store or rcu_assign_pointer), and the old version remains reachable to any reader that acquired a reference before the swap. Readers enter a read-side critical section with rcu_read_lock(), which on most implementations compiles down to nothing more than a compiler barrier — no atomic operation, no cache-line write, no lock contention. The cost is pushed entirely onto the writer and the reclamation subsystem.
The core mechanism enabling safe reclamation is the grace period: an interval during which the reclaimer waits until every CPU (or thread) that could have been in a pre-existing read-side critical section has passed through a quiescent state — a point guaranteed to be outside any RCU read section, such as a context switch, an idle loop, or an explicit checkpoint. Only after the grace period elapses is it safe to free the old version via synchronize_rcu() (blocking) or call_rcu() (deferred callback). This is functionally equivalent to epoch-based reclamation used in userspace concurrency libraries, and closely related to — but distinct from — hazard pointers.
- Writer starvation and memory bloat: if grace periods are delayed (e.g., a CPU is stuck in a long non-preemptible section, or a userspace thread never calls into a quiescent checkpoint), stale versions accumulate, causing unbounded memory growth until reclamation catches up.
- No blocking in read sections: a thread inside an RCU read-side critical section must never sleep or block on I/O, because that indefinitely extends the grace period for every writer waiting behind it — a classic implementation bug when RCU patterns are naively ported to userspace.
- Multiple concurrent versions: because old and new versions coexist during the grace period, readers may observe either version depending on when they entered — RCU provides eventual consistency of the pointer, not linearizable snapshot semantics across an entire structure.
- NUMA implications: the new version is typically allocated on the writer’s node, so cross-node readers pay a remote-access penalty until the structure is naturally re-migrated or explicitly node-local copies are maintained.
RCU is foundational to the Linux kernel’s networking stack (routing tables, netfilter rule sets, socket lookup tables) precisely because these structures are read millions of times per second per core but updated rarely. The same pattern appears in userspace high-throughput systems — DPDK forwarding tables, QUIC connection ID lookup tables, and Rust’s crossbeam-epoch crate, which implements an RCU-equivalent epoch-based reclamation scheme for lock-free data structures outside kernel context. Databases with MVCC-style version chains borrow the same conceptual model: readers walk versions without blocking writers, and a background vacuum/grace-period-equivalent process reclaims obsolete versions once no transaction can reference them.
Architecturally, adopting RCU is a trade of write latency and memory footprint for read scalability and cache efficiency, and that trade only pays off when the read:write ratio is heavily skewed and read-side latency is on a hot path sensitive to lock contention. Systems that adopt it without disciplined grace-period bookkeeping — or without auditing every code path for blocking calls inside read sections — tend to fail silently under load with slow, hard-to-diagnose memory growth rather than an obvious crash, making RCU one of the harder concurrency primitives to retrofit correctly into an existing codebase.