65.9K
CodeProject 正在变化。 阅读更多。
Home

并发对象池,正确的实现方式

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (12投票s)

2012年10月22日

CPOL

2分钟阅读

viewsIcon

38837

downloadIcon

1118

一个通用的、并发的对象池实现,具有智能内存管理。

引言

本文介绍了一个通用的、并发的对象池实现。这并非“又一个对象池”,而是经过精心设计和实现,具有以下特性:

  1. Generic
  2. 线程安全
  3. 无内存泄漏

此外,此实现提供了生产级别的代码。它经过了广泛的测试,并且运行良好。

背景

“对象池模式是一种软件创建型设计模式,它使用一组准备好的初始化对象,而不是按需分配和销毁它们。池的客户端将从池中请求一个对象,并在返回的对象上执行操作。当客户端完成时,它会将对象(一种特定类型的工厂对象)返回到池中,而不是销毁它。” -  维基百科

在 .NET 中,这种集合有几个优点:

  • 由于对象从不被释放,因此可以最大限度地减少 GC 需要收集的垃圾,从而缩短 GC 暂停时间。
  • 如果您正在分配/销毁大量对象,它可以提高性能。
  • 由于垃圾减少,内存碎片也随之减少。

使用代码 

使用 ConcurrentPool<T> 非常简单。首先,从 RecyclableObject 派生您的对象并实现 Recycle() 方法。或者,您可以自己实现 IRecyclable 接口。

public class MyObject : RecyclableObject
{
    /// <summary>
    /// Some dummy property
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// Resets the object state.
    /// </summary>
    public override void Recycle()
    {
        this.Name = String.Empty;    
    }
}

之后,您可能需要实例化 ConcurrentPool<T> 的一个实例。这可以通过多种方式完成,因为池需要能够自行分配对象。因此,如果在您的可回收对象上提供默认构造函数,您可以简单地实例化池,如下所示:

var pool = new ConcurrentPool<MyObject>("Pool of MyObject"); 

如果不是,您还可以提供一个 CreateInstanceDelegate  作为构造函数:

var pool = new ConcurrentPool<MyObject>("Pool of MyObject", () => new MyObject() ); 

最后,通过可释放模式获取和释放对象。

using (MyObject instance = pool.Acquire())
{
     // Do something with the instance
}
// The instance is released back to the pool 

或者,您可以调用 TryRelease() 尝试将对象释放回池中。

MyObject instance = pool.Acquire();
// Do something with the instance
instance.TryRelease();  

就是这样!

兴趣点 

现在,让我们看看我们的 ConcurrentPool<T> 是如何工作的。

在文章开头,我提到所提供的实现是通用的、线程安全的,并且确保不会发生内存泄漏。虽然第一点非常简单明了,但第二点是通过在 ConcurrentQueue<T> 中内部存储项目来实现的,该队列来自 .NET Framework 4+。

我们提到 RecyclableObject 实现了 IDisposable 模式,请考虑以下测试:

static void Test1()
{
    Console.WriteLine();
    Console.WriteLine(" Test 1");
    Console.WriteLine(" ******");
    Console.WriteLine();

    // Acquire an instance from the pool
    using (A a1 = poolOfA.Acquire())
    {
        Diagnose("a1 acquired.", poolOfA);

        // Do something with the instance
    }

    // Here the object is disposed and released back to the pool.
    Diagnose("a1 disposed.", poolOfA);
}

static void Test2()
{
    Console.WriteLine();
    Console.WriteLine(" Test 2");
    Console.WriteLine(" ******");
    Console.WriteLine();

    // Attempt to leak memory by allocating two in a function and never releasing the objects
    // manually to the pool.
    AllocateMany();

    // We don't use them and we did not dispose them yet...
    Diagnose("function ended.", poolOfA);

    // Now, let's force GC to collect
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();

    Thread.Sleep(100);

    // How many now?
    Diagnose("GC collected.", poolOfA);

}

static void AllocateMany()
{
    // Acquire more instances...
    for (int i = 0; i < 10; ++i)
    {
        A instance = poolOfA.Acquire();
    }
    Diagnose("10 instances acquired.", poolOfA);
}

在此代码片段中,Test2() 尝试通过不手动释放对象来泄漏内存。在这种情况下(由于我们实现了 IDisposable 模式),GC 会通过最终化对象来处理对象,而不是销毁它们,它们会被复活并被推回ConcurrentPool 中。

 > Pool created.
      Pool contains 0 items, 0 in use, 0 available.
 
 Test 1
 ******
 
 > a1 acquired.
      Pool contains 1 items, 1 in use, 0 available.
 > a1 disposed.
      Pool contains 1 items, 0 in use, 1 available.
 
 Test 2
 ******
 
 > 10 instances acquired.
      Pool contains 10 items, 10 in use, 0 available.
 > function ended.
      Pool contains 10 items, 10 in use, 0 available.
 > GC collected.
      Pool contains 10 items, 0 in use, 10 available. 

历史   

  • 2012年10月21日 - 初始发布。
© . All rights reserved.