Table of Contents

Bodu.Collections.Generic.Concurrent Namespace

Bodu.Collections.Generic.Concurrent

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-commit ItemEvicted event. 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 and ConcurrentHashSet<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, Count may transiently exceed Capacity by at most the number of in-flight writers, and GetOrAdd follows ConcurrentDictionary semantics - 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-flight GetOrAdd. Pick by which of those you cannot give up.
  • Reference types only in the ring. ConcurrentCircularBuffer<T> is constrained to where 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 with BlockingCollection<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.Collections package 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>.