Bodu.Collections.Generic.Concurrent Namespace
- Package
Purpose
Bodu.Collections.Generic.Concurrent ships the thread-safe / lock-free variants of the Bodu.Collections.Generic collections, in the Bodu.Collections.Concurrent package (which depends on Bodu.Collections, and through it on Bodu.Core). Reach for this namespace when the same collection is accessed by multiple producers and consumers and you need predictable concurrent semantics rather than an external lock.
Key types
- ConcurrentCircularBuffer<T> - thread-safe variant of CircularBuffer<T> implementing
IProducerConsumerCollection<T>over the Vyukov MPMC algorithm. Same overwrite semantics as the non-concurrent base. - ConcurrentHashSet<T> - thread-safe hash set with concurrent add / remove / contains; backed by a lock-free split-ordered list, so every operation completes without taking a lock.
- ConcurrentEvictingDictionary<TKey, TValue> - thread-safe variant of EvictingDictionary<TKey, TValue>: a fixed-capacity bounded cache over lock-striped segments supporting all six EvictingDictionaryPolicy values, optional TTL expiry (EvictingDictionaryExpiration), single-flight
GetOrAdd, and a post-commitItemEvictedevent. Eviction order is exact per segment and approximate globally;TKey : notnull. - ConcurrentLruCache<TKey, TValue> - read-optimized bounded cache with lock-free reads and a segmented pseudo-LRU policy (hot / warm / cold queues, as BitFaster.Caching and Caffeine use). Queue maintenance is amortized onto writers. Choose it over
ConcurrentEvictingDictionary<TKey,TValue>when read throughput dominates and approximate recency is acceptable;TKey : notnull.
Example
using Bodu.Collections.Generic.Concurrent;
// T is constrained to reference types (where T : class?) - wrap value types in a record or class.
var ring = new ConcurrentCircularBuffer<Sample>(capacity: 1024, allowOverwrite: true);
// Multi-producer / multi-consumer scenarios - no external lock needed.
Parallel.ForEach(samples, sample => ring.TryAdd(sample));
while (ring.TryTake(out Sample? item))
Process(item);
// Bounded, policy-driven cache shared by request threads.
var cache = new ConcurrentEvictingDictionary<string, Payload>(
capacity: 10_000,
policy: EvictingDictionaryPolicy.LeastRecentlyUsed);
Payload payload = cache.GetOrAdd(key, k => Load(k)); // single-flight: one loader per key at a time
Notes
- Lock-free ring and set; lock-striped cache.
ConcurrentCircularBuffer<T>uses the Vyukov bounded MPMC algorithm andConcurrentHashSet<T>a split-ordered list - neither takes a lock, so a preempted thread can never stall the others.ConcurrentEvictingDictionary<TKey,TValue>is different by design: it partitions the key space into independently locked segments, so contention is bounded by the stripe count rather than eliminated, and eviction order is exact within a segment but approximate across the whole cache. - Two bounded caches, two trade-offs. ConcurrentLruCache<TKey, TValue> keeps reads lock-free and pays for it in exactness: the policy is a pseudo-LRU,
Countmay transiently exceedCapacityby at most the number of in-flight writers, andGetOrAddfollowsConcurrentDictionarysemantics - racing callers may each run the factory, and one produced value wins. ConcurrentEvictingDictionary<TKey, TValue> takes a segment lock on every operation, including reads, and in exchange gives an exact per-segment policy, a strict capacity bound, TTL expiry, and a genuinely single-flightGetOrAdd. Pick by which of those you cannot give up. - Reference types only in the ring.
ConcurrentCircularBuffer<T>is constrained towhere T : class?because the Vyukov slots exchange references atomically; box or wrap value types. - Producer-consumer semantics. The ring implements
IProducerConsumerCollection<T>, so it composes withBlockingCollection<T>if you need blocking semantics on top. - Related namespaces. The non-concurrent peers (CircularBuffer<T> and the rest of the catalogue) live in Bodu.Collections.Generic, shipped by the
Bodu.Collectionspackage this one depends on. - See also: the concurrent collections guide, the circular buffer guide, and the Bodu.Collections.Concurrent introduction.
Classes
- ConcurrentCircularBuffer<T>
Provides a lock-free, bounded first-in, first-out (FIFO) buffer with optional overwrite semantics.
- ConcurrentEvictingDictionary<TKey, TValue>
Provides a thread-safe, fixed-capacity dictionary that automatically removes entries based on a chosen eviction policy, such as First-In-First-Out (FirstInFirstOut), Least Recently Used (LeastRecentlyUsed), or Least Frequently Used (LeastFrequentlyUsed).
- ConcurrentHashSet<T>
Provides a thread-safe, unordered set of unique elements.
- ConcurrentLruCache<TKey, TValue>
Provides a thread-safe, fixed-capacity cache with lock-free reads and a segmented pseudo-LRU eviction policy.
Structs
- ConcurrentCircularBuffer<T>.Enumerator
Provides a snapshot-based enumerator for ConcurrentCircularBuffer<T> that iterates over a stable point-in-time copy of the buffer's contents.
- ConcurrentEvictingDictionary<TKey, TValue>.Enumerator
Enumerates a point-in-time snapshot of a ConcurrentEvictingDictionary<TKey, TValue>.
- ConcurrentHashSet<T>.Enumerator
Enumerates a weakly consistent snapshot of a ConcurrentHashSet<T>.
- ConcurrentLruCache<TKey, TValue>.Enumerator
Enumerates a point-in-time snapshot of a ConcurrentLruCache<TKey, TValue>.