我的编程空间,编程开发者的网络收藏夹
学习永远不晚

.NET Core对象池的应用:设计篇

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

.NET Core对象池的应用:设计篇

《编程篇》已经涉及到了对象池模型的大部分核心接口和类型。对象池模型其实是很简单的,不过其中有一些为了提升性能而刻意为之的实现细节倒是值得我们关注。总的来说,对象池模型由三个核心对象构成,它们分别是表示对象池的ObjectPool<T>对象、对象值提供者的ObjectPoolProvider对象,已及控制池化对象创建与释放行为的IPooledObjectPolicy<T>对象,我们先来介绍最后一个对象。

一、 IPooledObjectPolicy<T>

我们在《编程篇》已经说过,表示池化对象策略的IPooledObjectPolicy<T>对象不仅仅帮助我们创建对象,还可以帮助我们执行一些对象回归对象池之前所需的回收操作,对象最终能否回到对象池中也受它的控制。如下面的代码片段所示,IPooledObjectPolicy<T>接口定义了两个方法,Create方法用来创建池化对象,对象回归前需要执行的操作体现在Return方法上,该方法的返回值决定了指定的对象是否应该回归对象池。抽象类PooledObjectPolicy<T>实现了该接口,我们一般将它作为自定义策略类型的基类。


public interface IPooledObjectPolicy<T>
{
    T Create();
    bool Return(T obj);
}

public abstract class PooledObjectPolicy<T> : IPooledObjectPolicy<T>
{
    protected PooledObjectPolicy(){}

    public abstract T Create();
    public abstract bool Return(T obj);
}

我们默认使用的是如下这个DefaultPooledObjectPolicy<T>类型,由于它直接通过反射来创建池化对象,所以要求泛型参数T必须有一个公共的默认无参构造函数。它的Return方法直接返回True,意味着提供的对象可以被无限制地复用。


public class DefaultPooledObjectPolicy<T> : PooledObjectPolicy<T> where T: class, new()
{
    public override T Create() => Activator.CreateInstance<T>();
    public override bool Return(T obj) => true;
}

二、ObjectPool<T>

对象池通过ObjectPool<T>对象表示。如下面的代码片段所示,ObjectPool<T>是一个抽象类,池化对象通过Get方法提供给我们,我们在使用完之后调用Return方法将其释放到对象池中以供后续复用。


public abstract class ObjectPool<T> where T: class
{
    protected ObjectPool(){}

    public abstract T Get();
    public abstract void Return(T obj);
}

DefaultObjectPool<T>

我们默认使用的对象池体现为一个DefaultObjectPool<T>对象,由于针对对象池的绝大部分实现就体现这个类型中,所以它也是本节重点讲述的内容。我们在前面一节已经说过,对象池具有固定的大小,并且默认的大小为处理器个数的2倍。我们假设对象池的大小为N,那么DefaultObjectPool<T>对象会如下图所示的方式使用一个单一对象和一个长度为N-1的数组来存放由它提供的N个对象。

如下面的代码片段所示,DefaultObjectPool<T>使用字段_firstItem用来存放第一个池化对象,余下的则存放在_items字段表示的数组中。值得注意的是,这个数组的元素类型并非池化对象的类型T,而是一个封装了池化对象的结构体ObjectWrapper。如果该数组元素类型改为引用类型T,那么当我们对某个元素进行复制的时候,运行时会进行类型校验(要求指定对象类型派生于T),无形之中带来了一定的性能损失(值类型数组就不需求进行派生类型的校验)。我们在前面提到过,对象池中存在一些性能优化的细节,这就是其中之一。


public class DefaultObjectPool<T> : ObjectPool<T> where T : class
{
    private protected T _firstItem;
    private protected readonly ObjectWrapper[]  _items;
    …
    private protected struct ObjectWrapper
    {
        public T Element;
    }
}

DefaultObjectPool<T>类型定义了如下两个构造函数。我们在创建一个DefaultObjectPool<T>对象的时候会提供一个IPooledObjectPolicy<T>对象并指定对象池的大小。对象池的大小默认设置为处理器数量的2倍体现在第一个构造函数重载中。如果指定的是一个DefaultPooledObjectPolicy<T>对象,表示默认池化对象策略的_isDefaultPolicy字段被设置成True。因为DefaultPooledObjectPolicy<T>对象的Return方法总是返回True,并且没有任何具体的操作,所以在将对象释放回对象池的时候就不需要调用Return方法了,这是第二个性能优化的细节。


public class DefaultObjectPool<T> : ObjectPool<T> where T : class
{    
    private protected T  _firstItem;
    private protected readonly ObjectWrapper[] _items;
    private protected readonly IPooledObjectPolicy<T> _policy;
    private protected readonly bool  _isDefaultPolicy;
    private protected readonly PooledObjectPolicy<T>  _fastPolicy;
    
    public DefaultObjectPool(IPooledObjectPolicy<T> policy) : this(policy, Environment.ProcessorCount * 2)
    {}

    public DefaultObjectPool(IPooledObjectPolicy<T> policy, int maximumRetained)
    {
        _policy  = policy ;
        _fastPolicy = policy as PooledObjectPolicy<T>;
        _isDefaultPolicy = IsDefaultPolicy();
        _items  = new ObjectWrapper[maximumRetained - 1];

        bool IsDefaultPolicy()
        {
            var type = policy.GetType();
            return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(DefaultPooledObjectPolicy<>);
        }
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private T Create() => _fastPolicy?.Create() ?? _policy.Create();
}

从第二个构造函数的定义可以看出,指定的IPooledObjectPolicy<T>对象除了会赋值给_policy字段之外,如果提供的是一个PooledObjectPolicy<T>对象,该对象还会同时赋值给另一个名为_fastPolicy的字段。在进行池化对象的提取和释放时,_fastPolicy字段表示的池化对象策略会优先选用,这个逻辑体现在Create方法上。因为调用类型的方法比调用接口方法具有更好的性能(所以该字段才会命名为_fastPolicy),这是第三个性能优化的细节。这个细节还告诉我们在自定义池化对象策略的时候,最好将PooledObjectPolicy<T>作为基类,而不是直接实现IPooledObjectPolicy<T>接口。

如下所示的是重写的Get和Return方法的定义。用于提供池化对象的Get方法很简单,它会采用原子操作使用Null将_firstItem字段表示的对象“替换”下来,如果该字段不为Null,那么将其作为返回的对象,反之它会遍历数组的每个ObjectWrapper对象,并使用Null将其封装的对象“替换”下来,第一个成功替换下来的对象将作为返回值。如果所有ObjectWrapper对象封装的对象都为Null,意味着所有对象都被“借出”或者尚未创建,此时返回创建的新对象了。


public class DefaultObjectPool<T> : ObjectPool<T> where T : class
{    
    public override T Get()
    {
        var item = _firstItem;
        if (item == null || Interlocked.CompareExchange(ref _firstItem, null, item) != item)
        {
            var items = _items;
            for (var i = 0; i < items.Length; i++)
            {
                item = items[i].Element;
                if (item != null && Interlocked.CompareExchange( ref items[i].Element, null, item) == item)
                {
                    return item;
                }
            }
            item = Create();
        }
        return item;
    }    

    public override void Return(T obj)
    {
        if (_isDefaultPolicy || (_fastPolicy?.Return(obj) ?? _policy.Return(obj)))
        {
            if (_firstItem != null || Interlocked.CompareExchange(ref _firstItem, obj, null) != null)
            {
                var items = _items;
                for (var i = 0; i < items.Length && Interlocked.CompareExchange( ref items[i].Element, obj, null) != null; ++i)
                {}
            }
        }
    }
    …
}

将对象释放会对象池的Return方法也很好理解。首先它需要判断指定的对象能否释放会对象池中,如果使用的是默认的池化对象策略,答案是肯定的,否则只能通过调用IPooledObjectPolicy<T>对象的Return方法来判断。从代码片段可以看出,这里依然会优先选择_fastPolicy字段表示的PooledObjectPolicy<T>对象以获得更好的性能。

在确定指定的对象可以释放回对象之后,如果_firstItem字段为Null,Return方法会采用原子操作使用指定的对象将其“替换”下来。如果该字段不为Null或者原子替换失败,该方法会便利数组的每个ObjectWrapper对象,并采用原子操作将它们封装的空引用替换成指定的对象。整个方法会在某个原子替换操作成功或者整个便利过程结束之后返回。

DefaultObjectPool<T>之所有使用一个数组附加一个单一对象来存储池化对象,是因为针对单一字段的读写比针对数组元素的读写具有更好的性能。从上面给出的代码可以看出,不论是Get还是Return方法,优先选择的都是_firstItem字段。如果池化对象的使用率不高,基本上使用的都会是该字段存储的对象,那么此时的性能是最高的。

DisposableObjectPool<T>

通过前面的示例演示我们知道,当池化对象类型实现了IDisposable接口的情况下,如果某个对象在回归对象池的时候,对象池已满,该对象将被丢弃。与此同时,被丢弃对象的Dispose方法将立即被调用。但是这种现象并没有在DefaultObjectPool<T>类型的代码中体现出来,这是为什么呢?实际上DefaultObjectPool<T>还有如下这个名为DisposableObjectPool<T>的派生类。如代码片段可以看出,表示池化对象类型的泛型参数T要求实现IDisposable接口。如果池化对象类型实现了IDisposable接口,通过默认ObjectPoolProvider对象创建的对象池就是一个DisposableObjectPool<T>对象。


internal sealed class DisposableObjectPool<T> : DefaultObjectPool<T>, IDisposable where T : class
{
    private volatile bool _isDisposed;
    public DisposableObjectPool(IPooledObjectPolicy<T> policy) : base(policy)
    {}

    public DisposableObjectPool(IPooledObjectPolicy<T> policy, int maximumRetained) : base(policy, maximumRetained)
    {}

    public override T Get()
    {
        if (_isDisposed)
        {
            throw new ObjectDisposedException(GetType().Name);
        }
        return base.Get();
    }

    public override void Return(T obj)
    {
        if (_isDisposed || !ReturnCore(obj))
        {
            DisposeItem(obj);
        }
    }

    private bool ReturnCore(T obj)
    {
        bool returnedToPool = false;
        if (_isDefaultPolicy || (_fastPolicy?.Return(obj) ?? _policy.Return(obj)))
        {
            if (_firstItem == null && Interlocked.CompareExchange(ref _firstItem, obj, null) == null)
            {
                returnedToPool = true;
            }
            else
            {
                var items = _items;
                for (var i = 0; i < items.Length && !(returnedTooPool = Interlocked.CompareExchange(ref items[i].Element, obj, null) == null); i++)
                {}
            }
        }
        return returnedTooPool;
    }

    public void Dispose()
    {
        _isDisposed = true;
        DisposeItem(_firstItem);
        _firstItem = null;

        ObjectWrapper[] items = _items;
        for (var i = 0; i < items.Length; i++)
        {
            DisposeItem(items[i].Element);
            items[i].Element = null;
        }
    }

    private void DisposeItem(T item)
    {
        if (item is IDisposable disposable)
        {
            disposable.Dispose();
        }
    }
}

从上面代码片段可以看出,DisposableObjectPool<T>自身类型也实现了IDisposable接口,它会在Dispose方法中调用目前对象池中的每个对象的Dispose方法。用于提供池化对象的Get方法除了会验证自身的Disposed状态之外,并没有特别之处。当对象未能成功回归对象池,通过调用该对象的Dispose方法将其释放的操作体现在重写的Return方法中。

三、ObjectPoolProvider

表示对象池的ObjectPool<T>对象是通过ObjectPoolProvider提供的。如下面的代码片段所示,抽象类ObjectPoolProvider定义了两个重载的Create<T>方法,抽象方法需要指定具体的池化对象策略。另一个重载由于采用默认的池化对象策略,所以要求对象类型具有一个默认无参构造函数。


public abstract class ObjectPoolProvider
{
    public ObjectPool<T> Create<T>() where T : class, new() => Create<T>(new DefaultPooledObjectPolicy<T>());
    public abstract ObjectPool<T> Create<T>(IPooledObjectPolicy<T> policy) where T : class;
}

在前面的示例演示中,我们使用的是如下这个DefaultObjectPoolProvider类型。如代码片段所示,DefaultObjectPoolProvider派生于抽象类ObjectPoolProvider,在重写的Create<T>方法中,它会根据泛型参数T是否实现IDisposable接口分别创建DisposableObjectPool<T>和DefaultObjectPool<T>对象。


public class DefaultObjectPoolProvider : ObjectPoolProvider
{
    public int MaximumRetained { get; set; } = Environment.ProcessorCount * 2;
    public override ObjectPool<T> Create<T>(IPooledObjectPolicy<T> policy) => typeof(IDisposable).IsAssignableFrom(typeof(T))
        ? new DisposableObjectPool<T>(policy, MaximumRetained) : new DefaultObjectPool<T>(policy, MaximumRetained);
}

DefaultObjectPoolProvider类型定义了一个标识对象池大小的MaximumRetained属性,采用处理器数量的两倍作为默认容量也体现在这里。这个属性并非只读,所以我们可以利用它根据具体需求调整提供对象池的大小。在ASP.NET应用中,我们基本上都会采用依赖注入的方式利用注入的ObjectPoolProvider对象来创建针对具体类型的对象池。我们在《编程篇》还演示了另一种创建对象池的方式,那就是直接调用ObjectPool类型的静态Create<T>方法,该方法的实现体现在如下所示的代码片段中。


public static class ObjectPool
{
    public static ObjectPool<T> Create<T>(IPooledObjectPolicy<T> policy) where T: class, new()
        => new DefaultObjectPoolProvider().Create<T>(policy ?? new DefaultPooledObjectPolicy<T>());
}

到目前为止,我们已经将整个对象池的设计模型进行了完整的介绍。总得来说,这是一个简单、高效并且具有可扩展性的对象池框架,该模型涉及的几个核心接口和类型体现在如下图所示的UML中。

.NET Core对象池的应用:编程篇

.NET Core对象池的应用:扩展篇

到此这篇关于.NET Core对象池的应用:设计篇的文章就介绍到这了,更多相关.NET Core对象池的应用内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

.NET Core对象池的应用:设计篇

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

.NET Core中对象池Object Pool的使用方法是什么

这篇文章主要讲解了“.NET Core中对象池Object Pool的使用方法是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“.NET Core中对象池Object Pool的使用方法是
2023-06-25

[转载]Java面向对象程序设计之接口应用

Java语言提供了一种接口(interface)机制。这种接口机制使Java的面向对象编程变得更加灵活。我们可以用接口来定义一个类的表现形式,但接口不能包含任何实现。在《Thinking in Java》一书中,作者对接口有这样的描述:“接
2023-06-03

面向对象设计模式在C++中的应用和注意事项

在 c++++ 中应用面向对象设计模式可以提升代码的可维护性和可重用性。单例模式确保只有一个类实例,工厂模式负责创建对象实例,无需指定具体类。应用设计模式时,注意不要过度使用、了解其意图、注意效率、选择轻量级的模式,并可结合模式创建灵活的解
面向对象设计模式在C++中的应用和注意事项
2024-05-14

C++ 成员函数详解:对象方法在设计模式中的应用

c++++ 成员函数在设计模式中的应用包括:封装数据、避免重复代码和提高可测试性。实战案例中,工厂模式通过成员函数实现:抽象产品接口定义共同行为,具体产品类实现具体行为,工厂根据类型创建产品,客户使用成员函数创建和使用产品。C++ 成员函数
C++ 成员函数详解:对象方法在设计模式中的应用
2024-04-29

递归在 C++ 面向对象编程中的应用:设计和实现指南

递归在 c++++ oop 中的设计和实现指南: 1. 识别基本情况:确定函数停止调用的情况。 2. 递归步骤:通过调用函数自身解决问题,直至简化为基本情况。 3. 注意事项:避免无限递归、优化递归过程、使用尾递归优化。 4. 实战案例:求
递归在 C++ 面向对象编程中的应用:设计和实现指南
2024-05-01

如何使用Python中的面向对象设计模式

如何使用Python中的面向对象设计模式,需要具体代码示例概述:在Python编程中,面向对象设计模式是非常重要的一个概念。它提供了一种结构化的方法来解决问题,并使得代码更易于理解、维护和扩展。本文将介绍几种常见的面向对象设计模式,并提供具
2023-10-22

C++ 友元函数详解:友元函数在面向对象设计中的应用?

c++++ 友元函数是一种特殊函数,可访问另一个类的私有和受保护成员。通过声明友元函数,非成员函数可以与特定类交互。友元函数的应用包括操作符重载、i/o 操作和底层实现。例如,友元函数可被用来重载 + 运算符,支持自定义数据类型之间的运算,
C++ 友元函数详解:友元函数在面向对象设计中的应用?
2024-04-29

“PHP 面向对象编程设计模式:理解 SOLID 原则及其应用”

SOLID 原则是面向对象编程设计模式中的一组指导原则,旨在提高软件设计的质量和可维护性。这些原则包括单一职责原则、开放-封闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。
“PHP 面向对象编程设计模式:理解 SOLID 原则及其应用”
2024-02-25

PHP设计模式:用于面向对象的解决方案

php 设计模式提供通用的解决方案来处理常见软件设计问题,提高代码的可扩展性、可维护性和灵活性。常见的 php 设计模式包括:策略模式:允许动态切换算法,适应不同的策略。单例模式:确保类只有一个实例,便于全局访问。观察者模式:允许对象订阅事
PHP设计模式:用于面向对象的解决方案
2024-05-14

C++ 函数调用面向对象设计:参数传递和返回值的对象传递

在 c++++ 函数调用中,参数可以使用值传递(接收参数副本)或引用传递(接收对实际对象的引用)。返回值同样可通过值传递或引用传递。值传递会复制对象,而引用传递会传递对象的引用,从而影响实际对象。C++ 函数调用面向对象设计:参数传递和返回
C++ 函数调用面向对象设计:参数传递和返回值的对象传递
2024-04-30

C++语言特性对设计模式应用的影响

c++++ 语言中多态性、模板编程和智能指针等特性对设计模式应用的影响包括:多态性:允许策略模式和抽象工厂模式等设计模式中不同类对象对相同调用做出不同响应。模板编程:用于创建处理不同类型事件的通用事件总线(观察者模式)和定义操作框架(模板方
C++语言特性对设计模式应用的影响
2024-05-13

网页设计中应用和技巧的绝对定位

绝对定位在网页设计中的应用与技巧在网页设计中,绝对定位是一种常用的布局方式。通过绝对定位,我们可以精确地控制元素的位置,使得网页的布局更加灵活多变。本文将介绍绝对定位的应用场景以及一些常用的技巧,并通过具体的代码示例进行说明。一、绝对定
网页设计中应用和技巧的绝对定位
2024-01-23

设计模式辅助编写面向对象代码的实用指南

设计模式是解决常见代码问题的预定义解决方案,分为创建型(创建对象)、结构型(组合类)、行为型(定义对象交互)三类。例如,工厂方法模式(创建型)定义创建对象的方法,但将实例化委托给子类。这种模式允许客户端只指定所需的产品类型,而无需了解其创建
设计模式辅助编写面向对象代码的实用指南
2024-05-10

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录