-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ObjectPool<T> #49680
Comments
Tagging subscribers to this area: @eiriktsarpalis Issue DetailsBackground and MotivationFollow up to #23700 (comment) Expose the bounded Additionally Proposed APInamespace System.Collections.Concurrent
{
/// <summary>
/// Provides a multi-producer, multi-consumer thread-safe bounded pool. When the pool is full,
/// enqueues fail and return false. When the pool is empty, dequeues fail and return null.
/// </summary>
[DebuggerDisplay("Capacity = {Capacity}")]
internal sealed class ObjectPool<T>
{
/// <summary>Creates the pool.</summary>
/// <param name="boundedLength">
/// The maximum number of elements the pool can contain. Must be a power of 2.
/// </param>
public ObjectPool(int boundedLength);
/// <summary>Gets the number of elements this pool can store.</summary>
public int Capacity;
/// <summary>Tries to dequeue an element from the pool.</summary>
public bool TryDequeue([MaybeNullWhen(false)] out T item);
/// <summary>
/// Attempts to enqueue the item. If successful, the item will be stored
/// in the pool and true will be returned; otherwise, the item won't be stored, and false
/// will be returned.
/// </summary>
public bool TryEnqueue(T item);
}
} Usage Examplesstatic readonly ObjectPool<MyObject> _pool = new (32);
public MyObject Rent() => _pool.TryDequeue(out var value) ? value : new ();
public void Return(MyObject value) => _pool.TryEnqueue(value); Alternative DesignsNone RisksPeople might want collection interfaces and enumeration.
|
Will it be fully thread safe or single-producer-single-consumer only? update: I confused ConcurrentQueueSegment with another type. |
When I was writing classes like this I would usually have the pool constructor accept a |
Is there any reason why the "return" method is named |
I think we should draw inspiration from https://github.com/dotnet/aspnetcore/tree/main/src/ObjectPool/src.
Splitting the responsibility between policy and pool allows consumers to change if an object is pool without changing how its stored. |
Background and Motivation
Follow up to #23700 (comment)
Expose the bounded
ConcurrentQueueSegment<T>
as anObjectPool<T>
type as usingConcurrentQueue<T>
as a bounded object pool is too hard as the counting mechanism becomes separate from the queue so its at best eventually consistent; either adding too many[1] (which is then removed), or pooling too few[2] due to the race between counting and enqueuing/dequeuing; depending if the decrement is before1.TryDequeue
(when it has to be aInterlocked.CompareExchange
spin) or after2 (which is a less expensiveInterlocked.Decrement
)Additionally
ConcurrentQueue<T>
has already paid the cost for strict bounding; which then a second phase of "atomic"ish bounding has to be shimmed onto; so there is additional cost in memory and indirections from the extra inConcurrentQueue<T>
plus the inexactness of the counting; which is unsatisfactory, since all the work has already been done for the scenario its just inaccessable.Proposed API
Usage Examples
Alternative Designs
Always enqueue, but overwrite first (oldest) in queue; however that introduces extra contention...
Risks
People might want collection interfaces and enumeration; but they are fungible objects, so why?
The text was updated successfully, but these errors were encountered: