线程池是从预先创建的众多线程中 取用 / 归还 线程,通过重复利用线程,以节省频繁新建销毁线程的开销。
同理,对象池则是为了从预先创建好的众多相同的对象中 取用 / 归还 对象,通过重复利用对象,以节省频繁创建销毁对象的开销。
对象池多用于并发多线程操作,因此对象池一般要求是线程安全的,而且还需要有很高的性能。
这里摘录一个在微软开源的Roslyn编译器中实现的ObjectPool(删除了部分调试代码):
using System;
using System.Diagnostics;
using System.Threading;
namespace Microsoft.CodeAnalysis.PooledObjects
{
///
/// Generic implementation of object pooling pattern with predefined pool size limit. The main purpose is that
/// limited number of frequently used objects can be kept in the pool for further recycling.
///
/// Notes:
/// 1) it is not the goal to keep all returned objects. Pool is not meant for storage. If there is no space in the
/// pool, extra returned objects will be dropped.
///
/// 2) it is implied that if object was obtained from a pool, the caller will return it back in a relatively short
/// time. Keeping checked out objects for long durations is ok, but reduces usefulness of pooling. Just new up your own.
///
/// Not returning objects to the pool in not detrimental to the pool's work, but is a bad practice. Rationale: If
/// there is no intent for reusing the object, do not use pool - just use "new".
///
public class ObjectPool where T : class
{
[DebuggerDisplay("{Value,nq}")]
private struct Element
{
internal T Value;
}
// Not using System.Func{T} because .NET2.0 does not have that type.
public delegate T Factory();
// Storage for the pool objects. The first item is stored in a dedicated field because we expect to be able to
// satisfy most requests from it.
private T _firstItem;
private readonly Element[] _items;
// factory is stored for the lifetime of the pool. We will call this only when pool needs to expand. compared to
// "new T()", Func gives more flexibility to implementers and faster than "new T()".
private readonly Factory _factory;
public ObjectPool(Factory factory)
: this(factory, Environment.ProcessorCount * 2)
{ }
public ObjectPool(Factory factory, int size)
{
Debug.Assert(size >= 1);
_factory = factory;
_items = new Element[size - 1];
}
private T CreateInstance()
{
T inst = _factory();
return inst;
}
///
/// Produces an instance.
///
///
/// Search strategy is a simple linear probing which is chosen for it cache-friendliness. Note that Free will try
/// to store recycled objects close to the start thus statistically reducing how far we will typically search.
///
public T Allocate()
{
// PERF: Examine the first element. If that fails, AllocateSlow will look at the remaining elements. Note
// that the initial read is optimistically not synchronized. That is intentional. We will interlock only when
// we have a candidate. in a worst case we may miss some recently returned objects. Not a big deal.
T inst = _firstItem;
if (inst == null || inst != Interlocked.CompareExchange(ref _firstItem, null, inst))
{
inst = AllocateSlow();
}
return inst;
}
private T AllocateSlow()
{
Element[] items = _items;
for (int i = 0; i < items.Length; i++)
{
// Note that the initial read is optimistically not synchronized. That is intentional. We will interlock
// only when we have a candidate. in a worst case we may miss some recently returned objects. Not a big deal.
T inst = items[i].Value;
if (inst != null)
{
if (inst == Interlocked.CompareExchange(ref items[i].Value, null, inst))
{
return inst;
}
}
}
return CreateInstance();
}
///
/// Returns objects to the pool.
///
///
/// Search strategy is a simple linear probing which is chosen for it cache-friendliness. Note that Free will try
/// to store recycled objects close to the start thus statistically reducing how far we will typically search in Allocate.
///
public void Free(T obj)
{
Validate(obj);
if (_firstItem == null)
{
// Intentionally not using interlocked here. In a worst case scenario two objects may be stored into same
// slot. It is very unlikely to happen and will only mean that one of the objects will get collected.
_firstItem = obj;
}
else
{
FreeSlow(obj);
}
}
private void FreeSlow(T obj)
{
Element[] items = _items;
for (int i = 0; i < items.Length; i++)
{
if (items[i].Value == null)
{
// Intentionally not using interlocked here. In a worst case scenario two objects may be stored into
// same slot. It is very unlikely to happen and will only mean that one of the objects will get collected.
items[i].Value = obj;
break;
}
}
}
[Conditional("DEBUG")]
private void Validate(object obj)
{
Debug.Assert(obj != null, "freeing null?");
Debug.Assert(_firstItem != obj, "freeing twice?");
var items = _items;
for (int i = 0; i < items.Length; i++)
{
var value = items[i].Value;
if (value == null)
{
return;
}
Debug.Assert(value != obj, "freeing twice?");
}
}
}
}
原地址链接
http://source.roslyn.codeplex.com/#Microsoft.CodeAnalysis/ObjectPool%25601.cs
这里面有一个技巧,看微软的对象池实现就会发现他使用了结构体对泛型类型进行了封装。
private struct Element
{
internal T Value;
}
private readonly Element[] _items;
详细解释参见下面链接的第一,二条技巧,当然里面也有使用List实现一个对象池
https://www.red-gate.com/simple-talk/dotnet/.net-framework/5-tips-and-techniques-for-avoiding-automatic-gc-collections/
引用链接文章中所讲:对于引用类型的数组,GC除了检查数组本身是否存活,还需要遍历数组检查每个元素是否还存活。
而对于结构体类型的数组,因为结构体不会为空,所以GC只要检查一下数组本身是否存活就可以了。
是一个对GC友好的技巧。
另外,Roslyn中实现了多种线程池,根据微软编译器组的人说法,性能最好的就是上面这个使用结构体对泛型类型进行了封装的这个ObjectPool
各种线程池的区别参见下文链接:
https://stackoverflow.com/questions/30618067/why-are-there-so-many-implementations-of-object-pooling-in-roslyn