Object Pooling(對象池)實現


在文章開始之前首先要思考的問題是為什么要建立對象池。這和.NET垃圾回收機制有關,正如下面引用所說,內存不是無限的,垃圾回收器最終要回收對象,釋放內存。盡管.NET為垃圾回收已經進行了大量優化,例如將托管堆划分為 3 Generations(代)並設定新建的對象回收的最快,新建的短生命周期對象將進入 Gen 0(新建對象大於或等於 85,000 字節將被看作大對象,直接進入 Gen 2),而 Gen 0 通常情況下分配比較小的內存,因此Gen 0 將回收的非常快。而高頻率進行垃圾回收導致 CPU 使用率過高,當 Gen 2 包含大量對象時,回收垃圾也將產生性能問題。

.NET 的垃圾回收器管理應用程序的內存分配和釋放。 每當有對象新建時,公共語言運行時都會從托管堆為對象分配內存。 只要托管堆中有地址空間,運行時就會繼續為新對象分配空間。 不過,內存並不是無限的。 垃圾回收器最終必須執行垃圾回收來釋放一些內存。 垃圾回收器的優化引擎會根據所執行的分配來確定執行回收的最佳時機。 執行回收時,垃圾回收器會在托管堆中檢查應用程序不再使用的對象,然后執行必要的操作來回收其內存。參考

構造對象池

.Net Core 在(Base Class Library)基礎類型中添加了 ArrayPool,但 ArrayPool 只適用於數組。針對自定義對象,參考MSDN有一個實現,但沒有初始化池大小,且從池里取對象的方式比較粗糙,完整的對象池應該包含:

  • 池大小
  • 初始化委托
  • 實例存取方式(FIFO、LIFO 等自定義方式,根據個人需求實現獲取實例方式)
  • 獲取實例策略

1. 定義對象存取接口,以實現多種存取策略,例如 FIFO、LIFO

/// <summary>
/// 對象存取方式
/// </summary>
public interface IAccessMode<T>
{
    /// <summary>
    /// 租用對象
    /// </summary>
    /// <returns></returns>
    /// <exception cref="InvalidOperationException"></exception>
    T Rent();
    
    /// <summary>
    /// 返回實例
    /// </summary>
    /// <param name="item"></param>
    void Return(T item);
}

2. 實現存取策略

FIFO

FIFO通過Queue實現,參考

public sealed class FIFOAccessMode<T> : Queue<T>, IAccessMode<T>
{
    private readonly int _capacity;
    private readonly Func<T> _func;
    private int _count;

    public FIFOAccessMode(int capacity, Func<T> func) : base(capacity)
    {
        _capacity = capacity;
        _func = func;
        InitialQueue();
    }

    public T Rent()
    {
        Interlocked.Increment(ref _count);
        return _capacity < _count ? _func.Invoke() : Dequeue();
    }

    public void Return(T item)
    {
        if (_count > _capacity)
        {
            var disposable = (IDisposable)item;
            disposable.Dispose();
        }
        else
        {
            Enqueue(item);
        }
        Interlocked.Decrement(ref _count);
    }

    private void InitialQueue()
    {
        for (var i = 0; i < _capacity; i++)
        {
            Enqueue(_func.Invoke());
        }
    }
}
LIFO

在LIFO中借助Stack特性實現進棧出棧,因此該策略繼承自Stack,參考

public sealed class LIFOAccessModel<T> : Stack<T>, IAccessMode<T>
{
    private readonly int _capacity;
    private readonly Func<T> _func;
    private int _count;

    public LIFOAccessModel(int capacity, Func<T> func) : base(capacity)
    {
        _capacity = capacity;
        _func = func;
        InitialStack();
    }

    public T Rent()
    {
        Interlocked.Increment(ref _count);
        return _capacity < _count ? _func.Invoke() : Pop();
    }

    public void Return(T item)
    {
        if (_count > _capacity)
        {
            var disposable = (IDisposable)item;
            disposable.Dispose();
        }
        else
        {
            Push(item);
        }
        Interlocked.Decrement(ref _count);
    }

    private void InitialStack()
    {
        for (var i = 0; i < _capacity; i++)
        {
            Push(_func.Invoke());
        }
    }
}

注意:以上兩個實現都遵循池容量不變原則,但租用的實例可以超過對象池大小,返還時還將檢測該實例直接釋放還是進入池中。而如何控制池大小和並發將在下面說明。

3.Pool實現

public class Pool<T> : IDisposable where T : IDisposable
{
    private int _capacity;
    private IAccessMode<T> _accessMode;
    private readonly object _locker = new object();
    private readonly Semaphore _semaphore;

    public Pool(AccessModel accessModel, int capacity, Func<T> func)
    {
        _capacity = capacity;
        _semaphore = new Semaphore(capacity, capacity);
        InitialAccessMode(accessModel, capacity, func);
    }

    private void InitialAccessMode(AccessModel accessModel, int capacity, Func<T> func)
    {
        switch (accessModel)
        {
            case AccessModel.FIFO:
                _accessMode = new FIFOAccessMode<T>(capacity, func);
                break;
            case AccessModel.LIFO:
                _accessMode = new LIFOAccessModel<T>(capacity, func);
                break;
            default:
                throw new NotImplementedException();
        }
    }

    public T Rent()
    {
        _semaphore.WaitOne();
        return _accessMode.Rent();
    }

    public void Return(T item)
    {
        _accessMode.Return(item);
        _semaphore.Release();
    }

    public void Dispose()
    {
        if (!typeof(IDisposable).IsAssignableFrom(typeof(T))) return;

        lock (_locker)
        {
            while (_capacity > 0)
            {
                var disposable = (IDisposable)_accessMode.Rent();
                _capacity--;
                disposable.Dispose();
            }

            _semaphore.Dispose();
        }
    }
}

在Pool中如何控制程序池並發,這里我們引入了 Semaphore 以控制並發,這里將嚴格控制程序池大小,避免內存溢出。

4.使用

Student 類用作測試

public class Student : IDisposable
{
    public string Name { get; set; }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private bool _disposed;

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed)
            return;

        if (disposing)
        {
            Name = null;
             //Free any other managed objects here.
        }

        _disposed = true;
    }
}
public void TestPool()
{
    Func<Student> func = NewStudent;
    var pool = new Pool<Student>(AccessModel.FIFO, 2, func);
    for (var i = 0; i < 3; i++)
    {
        Student temp = pool.Rent();
        //todo:Some operations
        pool.Return(temp);
    }

    Student temp1 = pool.Rent();

    pool.Return(temp1);

    pool.Dispose();
}

public Student NewStudent()
{
    return new Student();
}

總結:至此,一個完整的對象池建立完畢。

安裝與使用:現已發布到NuGet服務器,可在程序包管理控制台中輸入安裝命令使用。

Install-Package CustomObjectPool


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM