通用对象池ObjectPool的一种简易设计和实现方案

对象池,最简单直接的作用当然是通过池来减少创建和销毁对象次数,实现对象的缓存和复用。我们熟知的线程池、数据库连接池、TCP连接池等等都是非常典型的对象池。

一个基本的简易对象池的主要功能实现我认为应该至少包括以下三点:

1、对象的分配、销毁等管理策略

2、线程安全

3、性能

按照主要的常用功能,我们大致可以抽象出以下泛型接口IObjectPool<T>:

IObjectPoolusing System;

/// <summary>
/// Simple ObjectPool Interface
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IObjectPool<T> : IDisposable
{
    #region Properties

    /// <summary>
    /// Minimum enabled object count
    /// </summary>
    int MinObjCount { get; }

    /// <summary>
    /// Maximum enabled object count
    /// </summary>
    int MaxObjCount { get; }

    /// <summary>
    /// Current enabled object count
    /// </summary>
    int CurrentObjCount { get; }

    #endregion

    #region Methods

    /// <summary>
    /// Gets an item from the pool.
    /// </summary>
    /// <returns>The removed or created item.</returns>
    /// <remarks>If the pool is empty, a new item will be created and returned.</remarks>    
    T GetObject();

    /// <summary>
    /// Adds the provided item into the pool.
    /// </summary>
    /// <param name="item">The item to be added.</param>
    void PutObject(T item);

    #endregion

}

 

具体实现这个接口的时候,考虑到线程安全,同时为了降低线程安全控制的种种风险,我们使用framework4.0中提供的线程安全容器,示例代码中选择非常适合这种场景的线程安全队列ConcurrentQueue(时间和空间复杂度都非常合适):

ObjectPoolusing System;
using System.Collections.Concurrent;

public class ObjectPool<T> : IObjectPool<T> where T : new()
{
    #region 字段

    private readonly IProducerConsumerCollection<T> collection;

    private int _minObjCount = 0;

    private int _maxObjCount = 0;

    #endregion

    #region 构造函数

    public ObjectPool()
        : this(1, 8)
    {
    }

    public ObjectPool(int minObjCount, int maxObjCount)
    {
        if (minObjCount < 1)
        {
            throw new ArgumentException("minObjCount cannot be less than zero");
        }
        if (maxObjCount < 1)
        {
            throw new ArgumentException("maxObjCount cannot be less than zero");
        }
        if (maxObjCount < minObjCount)
        {
            throw new ArgumentException("maxObjCount cannot be less than minObjCount");
        }

        this._minObjCount = minObjCount;
        this._maxObjCount = maxObjCount;
        collection = new ConcurrentQueue<T>();
        var objCount = (minObjCount + maxObjCount) / 2;
        for (int i = 0; i < objCount; i++) //初始化的对象个数取折中方案
        {
            var item = new T();
            collection.TryAdd(item);
        }
    }

    #endregion

    #region 属性

    public int MinObjCount { get { return _minObjCount; } }

    public int MaxObjCount { get { return _maxObjCount; } }

    public int CurrentObjCount
    {
        get
        {
            if (collection != null)
            {
                return collection.Count;
            }
            return 0;
        }
    }

    #endregion

    #region 方法

    public T GetObject()
    {
        T item = default(T);
        var isOK = collection.TryTake(out item);

        //如取出对象为空 此处采用悲观对象分配方案,直接创建一个新对象,保证有对象可用
        if (item == null || isOK == false)
        {
            item = new T();
            this.PutObject(item); //新创建的对象放入容器中
            Console.WriteLine("新创建的对象放入容器中");
        }
        return item;
    }

    public void PutObject(T item)
    {
        if (item == null)
        {
            return;
        }
        collection.TryAdd(item);
        //TryTrimToMax(); //清除大于队列最大可用的项
    }

    public void Dispose()
    {
    }

    #endregion

    #region 辅助方法

    /// <summary>
    /// 清除大于队列最大可用的项
    /// </summary>
    private void TryTrimToMax()
    {
        while (this.collection.Count > this.MaxObjCount)
        {
            var item = default(T);
            this.collection.TryTake(out item);
            Console.WriteLine("清除了大于队列最大可用的项");
        }
    }

    #endregion

}

 

其中,GetObject方法即对象的获取至少有两种策略:

1、悲观对象创建策略
这种策略在获取对象时,“悲观”地认为当对象池中暂无可用对象时,等待是会发生很久时间的,必须保证能够返回一个可用对象,所以不如去掉等待直接创建一个对象返回。虽然并发较高的场景下这有可能造成系统中对象总数暂时超出了最大对象数量限制,但好处是我们可以保证系统有足够的对象可用,不会因为没可用对象或等待创建可用对象而使上层逻辑受阻。
对于对象超过最大对象个数限制的情况,我们完全可以对外再暴露一个接口方法(示例为TryTrimToMax),当系统并发压力减轻时,可调用该方法清理多余的对象。
上面ObjectPool示例代码中我们采取的就是悲观创建对象的方案。

 

2、乐观对象创建策略

这种策略“乐观”地认为,当对象池中暂无可用对象时,所有对象的消费者会在用完对象后及时的返回对象池中。
如果对象池中无可用对象,那么当前请求者可能会选择Sleep或者Wait或WaitOne等方式等待。如果更乐观的话,还可以采用类似lock-free的方式多尝试去获取对象(不完全是CAS的那种lock-free),直到获取对象为止。
这种方式的优点显而易见,它的空间使用率高,不会造成对象超过最大上限。但它的缺点也很明显,在一个高并发的可用对象已经供不应求的环境下,这种策略多数实现都不是wait-free的,没有获取到对象的请求调用只能排队等待,这就极大地降低了系统吞吐量,原来的对象池是为了提高性能的,现在则成了系统性能瓶颈的重要原因。

上述示例代码中,泛型参数T有where限制,可通过Func委托间接去掉这个限制,当然构造函数会略微变得复杂:

ObjectPoolusing System;
using System.Collections.Concurrent;

public class ObjectPool<T> : IObjectPool<T>
{
    #region 字段

    private readonly IProducerConsumerCollection<T> collection;

    private readonly Func<T> _generator;

    private int _minObjCount = 0;

    private int _maxObjCount = 0;

    #endregion

    #region 构造函数

    public ObjectPool(Func<T> generator)
        : this(generator, 1, 8)
    {
    }

    public ObjectPool(Func<T> generator, int minObjCount, int maxObjCount)
    {
        if (generator == null)
        {
            throw new ArgumentNullException("generator cannot be null");
        }
        if (minObjCount < 1)
        {
            throw new ArgumentException("minObjCount cannot be less than zero");
        }
        if (maxObjCount < 1)
        {
            throw new ArgumentException("maxObjCount cannot be less than zero");
        }
        if (maxObjCount < minObjCount)
        {
            throw new ArgumentException("maxObjCount cannot be less than minObjCount");
        }
        this._generator = generator;
        this._minObjCount = minObjCount;
        this._maxObjCount = maxObjCount;
        collection = new ConcurrentQueue<T>();
        var objCount = (minObjCount + maxObjCount) / 2;
        for (int i = 0; i < objCount; i++) //初始化的对象个数取折中方案
        {
            var item = _generator();
            collection.TryAdd(item);
        }
    }

    #endregion

    #region 属性

    public int MinObjCount { get { return _minObjCount; } }

    public int MaxObjCount { get { return _maxObjCount; } }

    public int CurrentObjCount
    {
        get
        {
            if (collection != null)
            {
                return collection.Count;
            }
            return 0;
        }
    }

    #endregion

    #region 方法

    public T GetObject()
    {
        T item = default(T);
        var isOK = collection.TryTake(out item);

        //如取出对象为空 此处采用悲观对象分配方案,直接创建一个新对象,保证有对象可用
        if (item == null || isOK == false)
        {
            item = _generator();
            this.PutObject(item); //新创建的对象放入容器中
            Console.WriteLine("新创建的对象放入容器中");
        }
        return item;
    }

    public void PutObject(T item)
    {
        if (item == null)
        {
            return;
        }
        collection.TryAdd(item);
        //TryTrimToMax(); //清除大于队列最大可用的项
    }

    public void Dispose()
    {
    }

    #endregion

    #region 辅助方法

    /// <summary>
    /// 清除大于队列最大可用的项
    /// </summary>
    private void TryTrimToMax()
    {
        while (this.collection.Count > this.MaxObjCount)
        {
            var item = default(T);
            this.collection.TryTake(out item);
            Console.WriteLine("清除了大于队列最大可用的项");
        }
    }

    #endregion

}

 

可通过下列代码进行测试验证:

ObjectPoolTestusing System;
using System.Threading;
using System.Threading.Tasks;

class UserService
{
    public void CheckUser(int i)
    {
        //Console.WriteLine("check user:{0}", i);
        Thread.Sleep(1);
    }
}

public class Program
{
    static void Main(string[] args)
    {
        TestObjectPool();

        for (int a = 0; a < 10; a++)
        {
            Console.WriteLine("测试:{0}", a + 1);
            TestObjectPool();
        }

        Console.Read();
    }

    static void TestObjectPool()
    {
        var recordCount = 10000;
        using (var pool = new ObjectPool<UserService>(() => { return new UserService(); }, 1, 16))
        //using (var pool = new ObjectPool<UserService>(() => { return new UserService(); }, 1, 128))
        {
            Console.WriteLine("Init:current obj count:{0}", pool.CurrentObjCount);
            Parallel.For(0, recordCount, i =>
            {
                var service = pool.GetObject();
                service.CheckUser(i);
                Thread.Sleep(2);
                pool.PutObject(service);
            });

            Console.WriteLine("current obj count:{0}", pool.CurrentObjCount);
            Console.WriteLine("min obj count:{0}", pool.MinObjCount);
            Console.WriteLine("max obj count:{0}", pool.MaxObjCount);
            Console.WriteLine("====================");
        }
    }
}

 

本文的对象池实现较为简洁,只有基本的存取功能,其他功能如对象的销毁等可以继续扩展。还要感慨下幸好有了线程安全容器,果然可以成倍地提高生产效率,而且代码更加简洁优雅。

 

参考:

http://blogs.msdn.com/b/pfxteam/archive/2010/04/13/9990427.aspx

http://code.msdn.microsoft.com/parextsamples

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当然,在这里给你一个更通用的Unity对象脚本。这个脚本可以用于管理任何类型的游戏对象。 ```csharp using System.Collections.Generic; using UnityEngine; public class ObjectPool<T> where T : Component { private readonly List<T> objects; // 对象中的对象列表 private readonly T prefab; // 要创建的对象的预制体 public ObjectPool(T prefab, int initialSize) { objects = new List<T>(); this.prefab = prefab; // 预先实例化一定数量的对象并加入对象 for (int i = 0; i < initialSize; i++) { CreateObject(); } } public T GetObject() { // 查找并返回未激活的对象 foreach (T obj in objects) { if (!obj.gameObject.activeInHierarchy) { obj.gameObject.SetActive(true); return obj; } } // 如果没有未激活的对象,则创建一个新的对象并加入对象 T newObj = CreateObject(); newObj.gameObject.SetActive(true); return newObj; } public void ReturnObject(T obj) { // 将对象设置为未激活状态并重置其属性 obj.gameObject.SetActive(false); obj.transform.position = Vector3.zero; obj.transform.rotation = Quaternion.identity; } private T CreateObject() { T newObj = Object.Instantiate(prefab); newObj.gameObject.SetActive(false); objects.Add(newObj); return newObj; } } ``` 使用方法: 1. 在你的脚本中,创建一个 `ObjectPool<T>` 的实例,其中 `T` 是你要管理的游戏对象的类型。 2. 在需要使用对象的地方,调用 `GetObject()` 方法从对象中获取对象,并在需要时使用。 3. 使用完对象后,调用 `ReturnObject(obj)` 方法将对象返回给对象,并重置其属性。 以下是一个使用示例: ```csharp public class ObjectPoolExample : MonoBehaviour { public GameObject prefab; public int initialSize; private ObjectPool<GameObject> objectPool; private void Start() { objectPool = new ObjectPool<GameObject>(prefab, initialSize); } private void Update() { if (Input.GetKeyDown(KeyCode.Space)) { GameObject obj = objectPool.GetObject(); // 使用对象 // ... // 使用完毕后返回对象 objectPool.ReturnObject(obj); } } } ``` 请注意,这个通用对象脚本可以管理任何类型的游戏对象,但在获取和返回对象时,需要将其强制转换为正确的类型。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值