简体   繁体   English

C#对象池模式实现

[英]C# Object Pooling Pattern implementation

Does anyone have a good resource on implementing a shared object pool strategy for a limited resource in vein of Sql connection pooling?有没有人在 Sql 连接池的脉络中为有限资源实施共享对象池策略的好资源? (ie would be implemented fully that it is thread safe). (即将完全实现它是线程安全的)。

To follow up in regards to @Aaronaught request for clarification the pool usage would be for load balancing requests to an external service.要跟进@Aaronaught 澄清请求,池使用将用于对外部服务的负载平衡请求。 To put it in a scenario that would probably be easier to immediately understand as opposed to my direct situtation.将它放在一个可能更容易立即理解的场景中,而不是我的直接情境。 I have a session object that functions similarly to the ISession object from NHibernate.我有一个会话对象,其功能类似于 NHibernate 的ISession对象。 That each unique session manages it's connection to the database.每个唯一的会话管理它与数据库的连接。 Currently I have 1 long running session object and am encountering issues where my service provider is rate limiting my usage of this individual session.目前我有 1 个长时间运行的会话对象,并且遇到了我的服务提供商限制我对这个单独会话的使用的问题。

Due to their lack of expectation that a single session would be treated as a long running service account they apparently treat it as a client that is hammering their service.由于他们不期望单个会话会被视为长期运行的服务帐户,因此他们显然将其视为正在锤击他们的服务的客户。 Which brings me to my question here, instead of having 1 individual session I would create a pool of different sessions and split the requests up to the service across those multiple sessions instead of creating a single focal point as I was previously doing.这让我想到了我的问题,而不是有 1 个单独的会话,我会创建一个不同的会话池,并将请求拆分到多个会话中的服务,而不是像我以前那样创建单个焦点。

Hopefully that background offers some value but to directly answer some of your questions:希望该背景提供一些价值,但要直接回答您的一些问题:

Q: Are the objects expensive to create?问:创建对象是否昂贵?
A: No objects are a pool of limited resources A:没有对象是有限资源池

Q: Will they be acquired/released very frequently?问:它们会非常频繁地被获取/发布吗?
A: Yes, once again they can be thought of NHibernate ISessions where 1 is usually acquired and released for the duration of every single page request. A:是的,它们可以再次被认为是 NHibernate ISessions,其中 1 通常在每个单个页面请求的持续时间内获取和释放。

Q: Will a simple first-come-first-serve suffice or do you need something more intelligent, ie that would prevent starvation?问:简单的先到先得就足够了,还是需要更智能的东西,即可以防止饥饿?
A: A simple round robin type distribution would suffice, by starvation I assume you mean if there are no available sessions that callers become blocked waiting for releases. A:一个简单的循环类型分发就足够了,我假设你的意思是如果没有可用的会话,调用者会被阻塞等待释放。 This isn't really applicable since the sessions can be shared by different callers.这并不真正适用,因为会话可以由不同的调用者共享。 My goal is distribute the usage across multiple sessions as opposed to 1 single session.我的目标是在多个会话中分配使用量,而不是 1 个单个会话。

I believe this is probably a divergence from a normal usage of an object pool which is why I originally left this part out and planned just to adapt the pattern to allow sharing of objects as opposed to allowing a starvation situation to ever occur.我相信这可能与对象池的正常使用不同,这就是为什么我最初忽略了这部分并计划调整模式以允许共享对象而不是允许饥饿情况发生。

Q: What about things like priorities, lazy vs. eager loading, etc.?问:诸如优先级、懒加载与急切加载之类的东西呢?
A: There is no prioritization involved, for simplicity's sake just assume that I would create the pool of available objects at the creation of the pool itself.答:不涉及优先级排序,为简单起见,假设我将在创建池本身时创建可用对象池。

This question is a little trickier than one might expect due to several unknowns: The behaviour of the resource being pooled, the expected/required lifetime of objects, the real reason that the pool is required, etc. Typically pools are special-purpose - thread pools, connection pools, etc. - because it is easier to optimize one when you know exactly what the resource does and more importantly have control over how that resource is implemented.由于以下几个未知因素,这个问题比人们想象的要棘手一些:被池化的资源的行为、对象的预期/要求的生命周期、需要池的真正原因等。通常池是特殊用途的 - 线程池、连接池等 - 因为当您确切地知道资源的作用并且更重要的是可以控制该资源的实现方式时,更容易优化一个。

Since it's not that simple, what I've tried to do is offer up a fairly flexible approach that you can experiment with and see what works best.由于它不是那么简单,我试图做的是提供一种相当灵活的方法,您可以尝试并查看哪种方法效果最好。 Apologies in advance for the long post, but there is a lot of ground to cover when it comes to implementing a decent general-purpose resource pool.提前为长篇道歉,但在实施一个体面的通用资源池方面有很多地方需要涵盖。 and I'm really only scratching the surface.而我真的只是触及了表面。

A general-purpose pool would have to have a few main "settings", including:通用池必须有一些主要的“设置”,包括:

  • Resource loading strategy - eager or lazy;资源加载策略——急切或懒惰;
  • Resource loading mechanism - how to actually construct one;资源加载机制——如何实际构建;
  • Access strategy - you mention "round robin" which is not as straightforward as it sounds;访问策略 - 您提到了“循环”,这并不像听起来那么简单; this implementation can use a circular buffer which is similar , but not perfect, because the pool has no control over when resources are actually reclaimed.此实现可以使用类似但不完美的循环缓冲区,因为池无法控制何时实际回收资源。 Other options are FIFO and LIFO;其他选项是先进先出和后进先出; FIFO will have more of a random-access pattern, but LIFO makes it significantly easier to implement a Least-Recently-Used freeing strategy (which you said was out of scope, but it's still worth mentioning). FIFO 将具有更多的随机访问模式,但 LIFO 使实施最近最少使用的释放策略变得更加容易(您说这超出了范围,但仍然值得一提)。

For the resource loading mechanism, .NET already gives us a clean abstraction - delegates.对于资源加载机制,.NET 已经给了我们一个干净的抽象——委托。

private Func<Pool<T>, T> factory;

Pass this through the pool's constructor and we're about done with that.通过池的构造函数传递它,我们就完成了。 Using a generic type with a new() constraint works too, but this is more flexible.使用带有new()约束的泛型类型也可以,但这样更灵活。


Of the other two parameters, the access strategy is the more complicated beast, so my approach was to use an inheritance (interface) based approach:在其他两个参数中,访问策略是更复杂的野兽,所以我的方法是使用基于继承(接口)的方法:

public class Pool<T> : IDisposable
{
    // Other code - we'll come back to this

    interface IItemStore
    {
        T Fetch();
        void Store(T item);
        int Count { get; }
    }
}

The concept here is simple - we'll let the public Pool class handle the common issues like thread-safety, but use a different "item store" for each access pattern.这里的概念很简单——我们将让公共Pool类处理线程安全等常见问题,但为每个访问模式使用不同的“项目存储”。 LIFO is easily represented by a stack, FIFO is a queue, and I've used a not-very-optimized-but-probably-adequate circular buffer implementation using a List<T> and index pointer to approximate a round-robin access pattern. LIFO 很容易用堆栈表示,FIFO 是一个队列,我使用了一个不太优化但可能足够的循环缓冲区实现,使用List<T>和索引指针来近似循环访问模式.

All of the classes below are inner classes of the Pool<T> - this was a style choice, but since these really aren't meant to be used outside the Pool , it makes the most sense.下面的所有类都是Pool<T>内部类 - 这是一种风格选择,但由于这些实际上并不打算在Pool之外使用,所以它最有意义。

    class QueueStore : Queue<T>, IItemStore
    {
        public QueueStore(int capacity) : base(capacity)
        {
        }

        public T Fetch()
        {
            return Dequeue();
        }

        public void Store(T item)
        {
            Enqueue(item);
        }
    }

    class StackStore : Stack<T>, IItemStore
    {
        public StackStore(int capacity) : base(capacity)
        {
        }

        public T Fetch()
        {
            return Pop();
        }

        public void Store(T item)
        {
            Push(item);
        }
    }

These are the obvious ones - stack and queue.这些是显而易见的 - 堆栈和队列。 I don't think they really warrant much explanation.我认为他们真的不需要太多解释。 The circular buffer is a little more complicated:循环缓冲区稍微复杂一点:

    class CircularStore : IItemStore
    {
        private List<Slot> slots;
        private int freeSlotCount;
        private int position = -1;

        public CircularStore(int capacity)
        {
            slots = new List<Slot>(capacity);
        }

        public T Fetch()
        {
            if (Count == 0)
                throw new InvalidOperationException("The buffer is empty.");

            int startPosition = position;
            do
            {
                Advance();
                Slot slot = slots[position];
                if (!slot.IsInUse)
                {
                    slot.IsInUse = true;
                    --freeSlotCount;
                    return slot.Item;
                }
            } while (startPosition != position);
            throw new InvalidOperationException("No free slots.");
        }

        public void Store(T item)
        {
            Slot slot = slots.Find(s => object.Equals(s.Item, item));
            if (slot == null)
            {
                slot = new Slot(item);
                slots.Add(slot);
            }
            slot.IsInUse = false;
            ++freeSlotCount;
        }

        public int Count
        {
            get { return freeSlotCount; }
        }

        private void Advance()
        {
            position = (position + 1) % slots.Count;
        }

        class Slot
        {
            public Slot(T item)
            {
                this.Item = item;
            }

            public T Item { get; private set; }
            public bool IsInUse { get; set; }
        }
    }

I could have picked a number of different approaches, but the bottom line is that resources should be accessed in the same order that they were created, which means that we have to maintain references to them but mark them as "in use" (or not).我可以选择许多不同的方法,但最重要的是资源应该按照创建它们的相同顺序访问,这意味着我们必须维护对它们的引用,但将它们标记为“使用中”(或不)。 In the worst-case scenario, only one slot is ever available, and it takes a full iteration of the buffer for every fetch.在最坏的情况下,只有一个插槽可用,并且每次获取都需要对缓冲区进行完整的迭代。 This is bad if you have hundreds of resources pooled and are acquiring and releasing them several times per second;如果您汇集了数百个资源并且每秒多次获取和释放它们,这将是糟糕的; not really an issue for a pool of 5-10 items, and in the typical case, where resources are lightly used, it only has to advance one or two slots.对于 5-10 个项目的池来说并不是真正的问题,并且在资源使用较少的典型情况下,它只需要推进一两个插槽。

Remember, these classes are private inner classes - that is why they don't need a whole lot of error-checking, the pool itself restricts access to them.请记住,这些类是私有的内部类——这就是为什么它们不需要大量的错误检查,池本身限制了对它们的访问。

Throw in an enumeration and a factory method and we're done with this part:加入一个枚举和一个工厂方法,我们就完成了这部分:

// Outside the pool
public enum AccessMode { FIFO, LIFO, Circular };

    private IItemStore itemStore;

    // Inside the Pool
    private IItemStore CreateItemStore(AccessMode mode, int capacity)
    {
        switch (mode)
        {
            case AccessMode.FIFO:
                return new QueueStore(capacity);
            case AccessMode.LIFO:
                return new StackStore(capacity);
            default:
                Debug.Assert(mode == AccessMode.Circular,
                    "Invalid AccessMode in CreateItemStore");
                return new CircularStore(capacity);
        }
    }

The next problem to solve is loading strategy.下一个要解决的问题是加载策略。 I've defined three types:我定义了三种类型:

public enum LoadingMode { Eager, Lazy, LazyExpanding };

The first two should be self-explanatory;前两个应该是不言自明的; the third is sort of a hybrid, it lazy-loads resources but doesn't actually start re-using any resources until the pool is full.第三种是一种混合,它延迟加载资源,但在池已满之前实际上不会开始重新使用任何资源。 This would be a good trade-off if you want the pool to be full (which it sounds like you do) but want to defer the expense of actually creating them until first access (ie to improve startup times).如果您希望池已满(听起来像您这样做)但希望将实际创建它们的费用推迟到第一次访问(即改善启动时间),这将是一个很好的权衡。

The loading methods really aren't too complicated, now that we have the item-store abstraction:加载方法真的不是太复杂,现在我们有了 item-store 抽象:

    private int size;
    private int count;

    private T AcquireEager()
    {
        lock (itemStore)
        {
            return itemStore.Fetch();
        }
    }

    private T AcquireLazy()
    {
        lock (itemStore)
        {
            if (itemStore.Count > 0)
            {
                return itemStore.Fetch();
            }
        }
        Interlocked.Increment(ref count);
        return factory(this);
    }

    private T AcquireLazyExpanding()
    {
        bool shouldExpand = false;
        if (count < size)
        {
            int newCount = Interlocked.Increment(ref count);
            if (newCount <= size)
            {
                shouldExpand = true;
            }
            else
            {
                // Another thread took the last spot - use the store instead
                Interlocked.Decrement(ref count);
            }
        }
        if (shouldExpand)
        {
            return factory(this);
        }
        else
        {
            lock (itemStore)
            {
                return itemStore.Fetch();
            }
        }
    }

    private void PreloadItems()
    {
        for (int i = 0; i < size; i++)
        {
            T item = factory(this);
            itemStore.Store(item);
        }
        count = size;
    }

The size and count fields above refer to the maximum size of the pool and the total number of resources owned by the pool (but not necessarily available ), respectively.上面的sizecount字段分别指的是池的最大大小和池拥有的资源总数(但不一定可用)。 AcquireEager is the simplest, it assumes that an item is already in the store - these items would be preloaded at construction, ie in the PreloadItems method shown last. AcquireEager是最简单的,它假设一个项目已经在商店中——这些项目将在构建时预加载,即在最后显示的PreloadItems方法中。

AcquireLazy checks to see if there are free items in the pool, and if not, it creates a new one. AcquireLazy检查池中是否有空闲项目,如果没有,则创建一个新项目。 AcquireLazyExpanding will create a new resource as long as the pool hasn't reached its target size yet.只要池尚未达到其目标大小, AcquireLazyExpanding就会创建一个新资源。 I've tried to optimize this to minimize locking, and I hope I haven't made any mistakes (I have tested this under multi-threaded conditions, but obviously not exhaustively).我试图优化它以最小化锁定,我希望我没有犯任何错误(我已经在多线程条件下测试过这个,但显然不是详尽无遗)。

You might be wondering why none of these methods bother checking to see whether or not the store has reached the maximum size.您可能想知道为什么这些方法都没有检查存储是否已达到最大大小。 I'll get to that in a moment.稍后我会讲到。


Now for the pool itself.现在是游泳池本身。 Here is the full set of private data, some of which has already been shown:这是完整的私人数据集,其中一些已经显示:

    private bool isDisposed;
    private Func<Pool<T>, T> factory;
    private LoadingMode loadingMode;
    private IItemStore itemStore;
    private int size;
    private int count;
    private Semaphore sync;

Answering the question I glossed over in the last paragraph - how to ensure we limit the total number of resources created - it turns out that the .NET already has a perfectly good tool for that, it's called Semaphore and it's designed specifically to allow a fixed number of threads access to a resource (in this case the "resource" is the inner item store).回答我在上一段中忽略的问题——如何确保我们限制创建的资源总数——结果证明 .NET 已经有一个非常好的工具,它被称为信号量,它专门设计用于允许固定的访问资源的线程数(在这种情况下,“资源”是内部项目存储)。 Since we're not implementing a full-on producer/consumer queue, this is perfectly adequate for our needs.由于我们没有实现完整的生产者/消费者队列,这完全满足我们的需求。

The constructor looks like this:构造函数如下所示:

    public Pool(int size, Func<Pool<T>, T> factory,
        LoadingMode loadingMode, AccessMode accessMode)
    {
        if (size <= 0)
            throw new ArgumentOutOfRangeException("size", size,
                "Argument 'size' must be greater than zero.");
        if (factory == null)
            throw new ArgumentNullException("factory");

        this.size = size;
        this.factory = factory;
        sync = new Semaphore(size, size);
        this.loadingMode = loadingMode;
        this.itemStore = CreateItemStore(accessMode, size);
        if (loadingMode == LoadingMode.Eager)
        {
            PreloadItems();
        }
    }

Should be no surprises here.这里应该没有惊喜。 Only thing to note is the special-casing for eager loading, using the PreloadItems method already shown earlier.唯一需要注意的是PreloadItems加载的特殊情况,使用前面已经显示的PreloadItems方法。

Since almost everything's been cleanly abstracted away by now, the actual Acquire and Release methods are really very straightforward:由于现在几乎所有内容都已完全抽象出来,因此实际的AcquireRelease方法非常简单:

    public T Acquire()
    {
        sync.WaitOne();
        switch (loadingMode)
        {
            case LoadingMode.Eager:
                return AcquireEager();
            case LoadingMode.Lazy:
                return AcquireLazy();
            default:
                Debug.Assert(loadingMode == LoadingMode.LazyExpanding,
                    "Unknown LoadingMode encountered in Acquire method.");
                return AcquireLazyExpanding();
        }
    }

    public void Release(T item)
    {
        lock (itemStore)
        {
            itemStore.Store(item);
        }
        sync.Release();
    }

As explained earlier, we're using the Semaphore to control concurrency instead of religiously checking the status of the item store.如前所述,我们使用Semaphore来控制并发性,而不是虔诚地检查项目存储的状态。 As long as acquired items are correctly released, there's nothing to worry about.只要正确释放获得的物品,就没有什么可担心的。

Last but not least, there's cleanup:最后但并非最不重要的是,有清理:

    public void Dispose()
    {
        if (isDisposed)
        {
            return;
        }
        isDisposed = true;
        if (typeof(IDisposable).IsAssignableFrom(typeof(T)))
        {
            lock (itemStore)
            {
                while (itemStore.Count > 0)
                {
                    IDisposable disposable = (IDisposable)itemStore.Fetch();
                    disposable.Dispose();
                }
            }
        }
        sync.Close();
    }

    public bool IsDisposed
    {
        get { return isDisposed; }
    }

The purpose of that IsDisposed property will become clear in a moment. IsDisposed属性的用途IsDisposed就会变得清晰。 All the main Dispose method really does is dispose the actual pooled items if they implement IDisposable .所有主要的Dispose方法真正做的是处理实际的池项目,如果它们实现IDisposable


Now you can basically use this as-is, with a try-finally block, but I'm not fond of that syntax, because if you start passing around pooled resources between classes and methods then it's going to get very confusing.现在你基本上可以按原样使用它,带有try-finally块,但我不喜欢这种语法,因为如果你开始在类和方法之间传递池资源,那么它会变得非常混乱。 It's possible that the main class that uses a resource doesn't even have a reference to the pool.使用资源的主类甚至可能没有对池的引用。 It really becomes quite messy, so a better approach is to create a "smart" pooled object.它确实变得非常混乱,因此更好的方法是创建一个“智能”池对象。

Let's say we start with the following simple interface/class:假设我们从以下简单的接口/类开始:

public interface IFoo : IDisposable
{
    void Test();
}

public class Foo : IFoo
{
    private static int count = 0;

    private int num;

    public Foo()
    {
        num = Interlocked.Increment(ref count);
    }

    public void Dispose()
    {
        Console.WriteLine("Goodbye from Foo #{0}", num);
    }

    public void Test()
    {
        Console.WriteLine("Hello from Foo #{0}", num);
    }
}

Here's our pretend disposable Foo resource which implements IFoo and has some boilerplate code for generating unique identities.这是我们假装的一次性Foo资源,它实现了IFoo并有一些用于生成唯一身份的样板代码。 What we do is to create another special, pooled object:我们要做的是创建另一个特殊的池化对象:

public class PooledFoo : IFoo
{
    private Foo internalFoo;
    private Pool<IFoo> pool;

    public PooledFoo(Pool<IFoo> pool)
    {
        if (pool == null)
            throw new ArgumentNullException("pool");

        this.pool = pool;
        this.internalFoo = new Foo();
    }

    public void Dispose()
    {
        if (pool.IsDisposed)
        {
            internalFoo.Dispose();
        }
        else
        {
            pool.Release(this);
        }
    }

    public void Test()
    {
        internalFoo.Test();
    }
}

This just proxies all of the "real" methods to its inner IFoo (we could do this with a Dynamic Proxy library like Castle, but I won't get into that).这只是将所有“真实”方法代理到其内部IFoo (我们可以使用像 Castle 这样的动态代理库来做到这一点,但我不会深入研究)。 It also maintains a reference to the Pool that creates it, so that when we Dispose this object, it automatically releases itself back to the pool.它还维护对创建它的Pool的引用,因此当我们Dispose这个对象时,它会自动将自己释放回池中。 Except when the pool has already been disposed - this means we are in "cleanup" mode and in this case it actually cleans up the internal resource instead.除非池已经被处理——这意味着我们处于“清理”模式,在这种情况下,它实际上是清理内部资源


Using the approach above, we get to write code like this:使用上述方法,我们可以编写如下代码:

// Create the pool early
Pool<IFoo> pool = new Pool<IFoo>(PoolSize, p => new PooledFoo(p),
    LoadingMode.Lazy, AccessMode.Circular);

// Sometime later on...
using (IFoo foo = pool.Acquire())
{
    foo.Test();
}

This is a very good thing to be able to do.这是一件非常好的事情。 It means that the code which uses the IFoo (as opposed to the code which creates it) does not actually need to be aware of the pool.这意味着使用IFoo的代码(而不是创建它的代码)实际上不需要知道池。 You can even inject IFoo objects using your favourite DI library and the Pool<T> as the provider/factory.您甚至可以使用您最喜欢的 DI 库和Pool<T>作为提供者/工厂来注入IFoo对象。


I've put the complete code on PasteBin for your copy-and-pasting enjoyment.我已将完整代码放在 PasteBin 上,供您复制和粘贴。 There's also a short test program you can use to play around with different loading/access modes and multithreaded conditions, to satisfy yourself that it's thread-safe and not buggy.还有一个简短的测试程序,您可以使用它来处理不同的加载/访问模式和多线程条件,以确保它是线程安全的并且没有错误。

Let me know if you have any questions or concerns about any of this.如果您对此有任何疑问或疑虑,请告诉我。

Object Pooling in .NET Core .NET Core 中的对象池

The dotnet core has an implementation of object pooling added to the base class library (BCL). dotnet 核心具有添加到基类库 (BCL) 的对象池实现。 You can read the original GitHub issue here and view the code for System.Buffers .您可以在此处阅读原始 GitHub 问题并查看System.Buffers的代码。 Currently the ArrayPool is the only type available and is used to pool arrays.目前ArrayPool是唯一可用的类型,用于池化数组。 There is a nice blog post here .有一个很好的博客文章在这里

namespace System.Buffers
{
    public abstract class ArrayPool<T>
    {
        public static ArrayPool<T> Shared { get; internal set; }

        public static ArrayPool<T> Create(int maxBufferSize = <number>, int numberOfBuffers = <number>);

        public T[] Rent(int size);

        public T[] Enlarge(T[] buffer, int newSize, bool clearBuffer = false);

        public void Return(T[] buffer, bool clearBuffer = false);
    }
}

An example of its usage can be seen in ASP.NET Core.可以在 ASP.NET Core 中看到其用法示例。 Because it is in the dotnet core BCL, ASP.NET Core can share it's object pool with other objects such as Newtonsoft.Json's JSON serializer.因为它在 dotnet core BCL 中,所以 ASP.NET Core 可以与其他对象共享它的对象池,例如 Newtonsoft.Json 的 JSON 序列化程序。 You can read this blog post for more information on how Newtonsoft.Json is doing this.您可以阅读这篇博文,了解有关 Newtonsoft.Json 如何做到这一点的更多信息。

Object Pooling in Microsoft Roslyn C# Compiler Microsoft Roslyn C# 编译器中的对象池

The new Microsoft Roslyn C# compiler contains the ObjectPool type, which is used to pool frequently used objects which would normally get new'ed up and garbage collected very often.新的 Microsoft Roslyn C# 编译器包含ObjectPool类型,该类型用于池化经常使用的对象,这些对象通常会被频繁更新和垃圾收集。 This reduces the amount and size of garbage collection operations which have to happen.这减少了必须发生的垃圾收集操作的数量和大小。 There are a few different sub-implementations all using ObjectPool (See: Why are there so many implementations of Object Pooling in Roslyn? ).有几个不同的子实现都使用 ObjectPool(请参阅: 为什么 Roslyn 中有这么多对象池的实现? )。

1 - SharedPools - Stores a pool of 20 objects or 100 if the BigDefault is used. 1 - SharedPools - 如果使用 BigDefault,则存储 20 个对象或 100 个对象的池。

// Example 1 - In a using statement, so the object gets freed at the end.
using (PooledObject<Foo> pooledObject = SharedPools.Default<List<Foo>>().GetPooledObject())
{
    // Do something with pooledObject.Object
}

// Example 2 - No using statement so you need to be sure no exceptions are not thrown.
List<Foo> list = SharedPools.Default<List<Foo>>().AllocateAndClear();
// Do something with list
SharedPools.Default<List<Foo>>().Free(list);

// Example 3 - I have also seen this variation of the above pattern, which ends up the same as Example 1, except Example 1 seems to create a new instance of the IDisposable [PooledObject<T>][4] object. This is probably the preferred option if you want fewer GC's.
List<Foo> list = SharedPools.Default<List<Foo>>().AllocateAndClear();
try
{
    // Do something with list
}
finally
{
    SharedPools.Default<List<Foo>>().Free(list);
}

2 - ListPool and StringBuilderPool - Not strictly separate implementations but wrappers around the SharedPools implementation shown above specifically for List and StringBuilder's. 2 - ListPoolStringBuilderPool - 不是严格分开的实现,而是上面显示的 SharedPools 实现的包装器,专门用于 List 和 StringBuilder。 So this re-uses the pool of objects stored in SharedPools.所以这会重用存储在 SharedPools 中的对象池。

// Example 1 - No using statement so you need to be sure no exceptions are thrown.
StringBuilder stringBuilder= StringBuilderPool.Allocate();
// Do something with stringBuilder
StringBuilderPool.Free(stringBuilder);

// Example 2 - Safer version of Example 1.
StringBuilder stringBuilder= StringBuilderPool.Allocate();
try
{
    // Do something with stringBuilder
}
finally
{
    StringBuilderPool.Free(stringBuilder);
}

3 - PooledDictionary and PooledHashSet - These use ObjectPool directly and have a totally separate pool of objects. 3 - PooledDictionaryPooledHashSet - 它们直接使用 ObjectPool 并具有完全独立的对象池。 Stores a pool of 128 objects.存储 128 个对象的池。

// Example 1
PooledHashSet<Foo> hashSet = PooledHashSet<Foo>.GetInstance()
// Do something with hashSet.
hashSet.Free();

// Example 2 - Safer version of Example 1.
PooledHashSet<Foo> hashSet = PooledHashSet<Foo>.GetInstance()
try
{
    // Do something with hashSet.
}
finally
{
    hashSet.Free();
}

Microsoft.IO.RecyclableMemoryStream Microsoft.IO.RecyclableMemoryStream

This library provides pooling for MemoryStream objects.该库为MemoryStream对象提供池化。 It's a drop-in replacement for System.IO.MemoryStream .它是System.IO.MemoryStream替代品。 It has exactly the same semantics.它具有完全相同的语义。 It was designed by Bing engineers.它是由必应工程师设计的。 Read the blog post here or see the code on GitHub .阅读此处的博客文章或查看GitHub 上的代码。

var sourceBuffer = new byte[]{0,1,2,3,4,5,6,7}; 
var manager = new RecyclableMemoryStreamManager(); 
using (var stream = manager.GetStream()) 
{ 
    stream.Write(sourceBuffer, 0, sourceBuffer.Length); 
}

Note that RecyclableMemoryStreamManager should be declared once and it will live for the entire process–this is the pool.请注意, RecyclableMemoryStreamManager应该声明一次,并且它会在整个进程中存活——这就是池。 It is perfectly fine to use multiple pools if you desire.如果您愿意,可以使用多个池。

Something like this might suit your needs.像这样的东西可能适合您的需求。

/// <summary>
/// Represents a pool of objects with a size limit.
/// </summary>
/// <typeparam name="T">The type of object in the pool.</typeparam>
public sealed class ObjectPool<T> : IDisposable
    where T : new()
{
    private readonly int size;
    private readonly object locker;
    private readonly Queue<T> queue;
    private int count;


    /// <summary>
    /// Initializes a new instance of the ObjectPool class.
    /// </summary>
    /// <param name="size">The size of the object pool.</param>
    public ObjectPool(int size)
    {
        if (size <= 0)
        {
            const string message = "The size of the pool must be greater than zero.";
            throw new ArgumentOutOfRangeException("size", size, message);
        }

        this.size = size;
        locker = new object();
        queue = new Queue<T>();
    }


    /// <summary>
    /// Retrieves an item from the pool. 
    /// </summary>
    /// <returns>The item retrieved from the pool.</returns>
    public T Get()
    {
        lock (locker)
        {
            if (queue.Count > 0)
            {
                return queue.Dequeue();
            }

            count++;
            return new T();
        }
    }

    /// <summary>
    /// Places an item in the pool.
    /// </summary>
    /// <param name="item">The item to place to the pool.</param>
    public void Put(T item)
    {
        lock (locker)
        {
            if (count < size)
            {
                queue.Enqueue(item);
            }
            else
            {
                using (item as IDisposable)
                {
                    count--;
                }
            }
        }
    }

    /// <summary>
    /// Disposes of items in the pool that implement IDisposable.
    /// </summary>
    public void Dispose()
    {
        lock (locker)
        {
            count = 0;
            while (queue.Count > 0)
            {
                using (queue.Dequeue() as IDisposable)
                {

                }
            }
        }
    }
}

Example Usage示例用法

public class ThisObject
{
    private readonly ObjectPool<That> pool = new ObjectPool<That>(100);

    public void ThisMethod()
    {
        var that = pool.Get();

        try
        { 
            // Use that ....
        }
        finally
        {
            pool.Put(that);
        }
    }
}

Back in the day Microsoft provided a framework through Microsoft Transaction Server (MTS) and later COM+ to do object pooling for COM objects.回到过去,Microsoft 通过 Microsoft Transaction Server (MTS) 和后来的 COM+ 提供了一个框架来为 COM 对象进行对象池。 That functionality was carried forward to System.EnterpriseServices in the .NET Framework and now in Windows Communication Foundation. .NET Framework 中的 System.EnterpriseServices 以及现在的 Windows Communication Foundation 中都继承了该功能。

Object Pooling in WCF WCF 中的对象池

This article is from .NET 1.1 but should still apply in the current versions of the Framework (even though WCF is the preferred method).本文来自 .NET 1.1,但仍应适用于当前版本的 Framework(即使 WCF 是首选方法)。

Object Pooling .NET 对象池.NET

I really like Aronaught's implementation -- especially since he handles the waiting on resource to become available through the use of a semaphore.我真的很喜欢 Aronaught 的实现——特别是因为他通过使用信号量来处理等待资源变得可用。 There are several additions I would like to make:我想补充几点:

  1. Change sync.WaitOne() to sync.WaitOne(timeout) and expose the timeout as a parameter on Acquire(int timeout) method.sync.WaitOne()更改为sync.WaitOne(timeout)并将sync.WaitOne(timeout)公开为Acquire(int timeout)方法的参数。 This would also necessitate handling the condition when the thread times out waiting on an object to become available.当线程超时等待对象变为可用时,这也需要处理条件。
  2. Add Recycle(T item) method to handle situations when an object needs to be recycled when a failure occurs, for example.例如,添加Recycle(T item)方法来处理发生故障时需要回收对象的情况。

This is another implementation, with limited number of objects in pool.这是另一种实现,池中的对象数量有限。

public class ObjectPool<T>
    where T : class
{
    private readonly int maxSize;
    private Func<T> constructor;
    private int currentSize;
    private Queue<T> pool;
    private AutoResetEvent poolReleasedEvent;

    public ObjectPool(int maxSize, Func<T> constructor)
    {
        this.maxSize = maxSize;
        this.constructor = constructor;
        this.currentSize = 0;
        this.pool = new Queue<T>();
        this.poolReleasedEvent = new AutoResetEvent(false);
    }

    public T GetFromPool()
    {
        T item = null;
        do
        {
            lock (this)
            {
                if (this.pool.Count == 0)
                {
                    if (this.currentSize < this.maxSize)
                    {
                        item = this.constructor();
                        this.currentSize++;
                    }
                }
                else
                {
                    item = this.pool.Dequeue();
                }
            }

            if (null == item)
            {
                this.poolReleasedEvent.WaitOne();
            }
        }
        while (null == item);
        return item;
    }

    public void ReturnToPool(T item)
    {
        lock (this)
        {
            this.pool.Enqueue(item);
            this.poolReleasedEvent.Set();
        }
    }
}

Java oriented, this article expose the connectionImpl pool pattern and the abstracted object pool pattern and could be a good first approach : http://www.developer.com/design/article.php/626171/Pattern-Summaries-Object-Pool.htm面向 Java,本文公开了 connectionImpl 池模式和抽象对象池模式,可能是一个很好的第一种方法: http : //www.developer.com/design/article.php/626171/Pattern-Summaries-Object-Pool。 htm

Object pool Pattern:对象池模式:

图案

Does anyone have a good resource on implementing a shared object pool strategy for a limited resource in vein of Sql connection pooling?有没有人在 Sql 连接池的脉络中为有限资源实施共享对象池策略的好资源? (ie would be implemented fully that it is thread safe). (即将完全实现它是线程安全的)。

To follow up in regards to @Aaronaught request for clarification the pool usage would be for load balancing requests to an external service.要跟进@Aaronaught 澄清请求,池使用将用于对外部服务的负载平衡请求。 To put it in a scenario that would probably be easier to immediately understand as opposed to my direct situtation.将它放在一个可能更容易立即理解的场景中,而不是我的直接情境。 I have a session object that functions similarly to the ISession object from NHibernate.我有一个会话对象,其功能类似于 NHibernate 的ISession对象。 That each unique session manages it's connection to the database.每个唯一的会话管理它与数据库的连接。 Currently I have 1 long running session object and am encountering issues where my service provider is rate limiting my usage of this individual session.目前我有 1 个长时间运行的会话对象,并且遇到了我的服务提供商限制我对这个单独会话的使用的问题。

Due to their lack of expectation that a single session would be treated as a long running service account they apparently treat it as a client that is hammering their service.由于他们不期望单个会话会被视为长期运行的服务帐户,因此他们显然将其视为正在锤击他们的服务的客户。 Which brings me to my question here, instead of having 1 individual session I would create a pool of different sessions and split the requests up to the service across those multiple sessions instead of creating a single focal point as I was previously doing.这让我想到了我的问题,而不是有 1 个单独的会话,我会创建一个不同的会话池,并将请求拆分到多个会话中的服务,而不是像我以前那样创建单个焦点。

Hopefully that background offers some value but to directly answer some of your questions:希望该背景提供一些价值,但要直接回答您的一些问题:

Q: Are the objects expensive to create?问:创建对象是否昂贵?
A: No objects are a pool of limited resources A:没有对象是有限资源池

Q: Will they be acquired/released very frequently?问:它们会非常频繁地被获取/发布吗?
A: Yes, once again they can be thought of NHibernate ISessions where 1 is usually acquired and released for the duration of every single page request. A:是的,它们可以再次被认为是 NHibernate ISessions,其中 1 通常在每个单个页面请求的持续时间内获取和释放。

Q: Will a simple first-come-first-serve suffice or do you need something more intelligent, ie that would prevent starvation?问:简单的先到先得就足够了,还是需要更智能的东西,即可以防止饥饿?
A: A simple round robin type distribution would suffice, by starvation I assume you mean if there are no available sessions that callers become blocked waiting for releases. A:一个简单的循环类型分发就足够了,我假设你的意思是如果没有可用的会话,调用者会被阻塞等待释放。 This isn't really applicable since the sessions can be shared by different callers.这并不真正适用,因为会话可以由不同的调用者共享。 My goal is distribute the usage across multiple sessions as opposed to 1 single session.我的目标是在多个会话中分配使用情况,而不是 1 个单个会话。

I believe this is probably a divergence from a normal usage of an object pool which is why I originally left this part out and planned just to adapt the pattern to allow sharing of objects as opposed to allowing a starvation situation to ever occur.我相信这可能与对象池的正常使用不同,这就是为什么我最初忽略了这部分并计划调整模式以允许共享对象而不是允许饥饿情况发生。

Q: What about things like priorities, lazy vs. eager loading, etc.?问:诸如优先级、懒加载与急切加载之类的东西呢?
A: There is no prioritization involved, for simplicity's sake just assume that I would create the pool of available objects at the creation of the pool itself.答:不涉及优先级排序,为简单起见,假设我将在创建池本身时创建可用对象池。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM