并发对象池,正确的实现方式
一个通用的、并发的对象池实现,具有智能内存管理。
- 下载源代码 - 22.9 KB (VS2012 项目)
- 下载演示 - 9.3 KB
引言
本文介绍了一个通用的、并发的对象池实现。这并非“又一个对象池”,而是经过精心设计和实现,具有以下特性:
- Generic
- 线程安全
- 无内存泄漏
此外,此实现提供了生产级别的代码。它经过了广泛的测试,并且运行良好。
背景
“对象池模式是一种软件创建型设计模式,它使用一组准备好的初始化对象,而不是按需分配和销毁它们。池的客户端将从池中请求一个对象,并在返回的对象上执行操作。当客户端完成时,它会将对象(一种特定类型的工厂对象)返回到池中,而不是销毁它。” - 维基百科。
在 .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日 - 初始发布。