文章详情

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

请输入下面的图形验证码

提交验证

短信预约提醒成功

对象池的使用场景以及自动回收技术

2024-12-14 00:46

关注

而这个对象有可能创建的时候会需要构建很多资源,消耗比较大, 比如:在hiredis的SDK中每次都创建一个redisContext,如果需要查询,那就首先要进行网络连接。如果一直都是上图的工作方式,那将会频繁的创建连接,查询完毕后再释放连接。重新建立连接,让网络的查询效率降低。

这个时候就可以构建一个对象池来重复利用这个对象,并且一般要做到线程安全:

  1. 从对象池中获取对象,如果没有对象,则创建一个,并返回
  2. 使用对象
  3. 使用完成对象后,将对象还回对象池

那么符合如下条件的,应该适合使用对象池技术:

对象池的实现

首先介绍一下程序的样例对象Object, 其就接受一个初始化参数strInit。

  1. class Object 
  2. public
  3.   Object(std::string strInit) : m_strInit(strInit)  
  4.   {  
  5.     std::cout << "Object()" << std::endl;  
  6.   } 
  7.   virtual ~Object()  
  8.   {  
  9.     std::cout << "~Object()" << std::endl; 
  10.   } 
  11. private: 
  12.   std::string m_strInit; 
  13. }; 

先来看看对象池的类图:

然后再来看看代码吧:

  1. class ObjectPool 
  2. public
  3.   ObjectPool() { ; } 
  4.   ~ObjectPool() { ; } 
  5.   std::shared_ptr GetObject(std::string strInit) 
  6.   { 
  7.     std::shared_ptr pObject; 
  8.     { 
  9.       std::lock_guard guard(m_mutex); 
  10.       if (!m_lObjects.empty()) 
  11.       { 
  12.         pObject = m_lObjects.front(); 
  13.         m_lObjects.pop_front(); 
  14.       } 
  15.     } 
  16.  
  17.     if (!pObject) 
  18.     { 
  19.       pObject = std::make_shared(strInit); 
  20.     } 
  21.     return pObject; 
  22.   } 
  23.  
  24.   void ReturnObject(std::shared_ptr pObject) 
  25.     if (!pObject) 
  26.       return
  27.  
  28.     std::lock_guard guard(m_mutex); 
  29.     m_lObjects.push_front(pObject); 
  30.   } 
  31.  
  32. private: 
  33.   std::mutex m_mutex; 
  34.   std::list> m_lObjects; 
  35. }; 
  36. 那么使用起来比较简单,如下所示。

    1. ObjectPool objPool; 
    2.   auto pObj1 = objPool.GetObject("abc"); 
    3.   //操作对象完成任务 
    4.   //...... 
    5.   objPool.ReturnObject(pObj1); 

    但是要注意一点,有时候可能使用完了,却忘记调用ReturnObject了,这个时候是否想起了RAII技术《C++ RAII实现golang的defer》和《从lock_guard来说一说C++常用的RAII》。

    那么问一问,可以实现一个自动回收的对象池吗?不需要调用者在对象使用完成后,手动将对象归还给对象池,并且你可能要问:

    1. 针对不同类型的Object,是不是可以用模板去实现更加通用的实现一个对象池
    2. 构造函数的参数列表,也可以是任意的形式

    自动回收的对象池

    要实现自动回收的对象池,首先要了解unique_ptr和shared_ptr都可以自定义删除器,也就是说,比如当从对象池获取到的对象是用智能指针包裹的,一般默认的删除器为delete,那我们可以自义定删除器为: 将这个对象重新放回到对象池. 代码如下:

    1. template 
    2. class ObjectPool 
    3. public
    4.   ObjectPool() 
    5.   { 
    6.     m_fObjDeleter = [&](T* pObj) { 
    7.       if (m_bDeconstruct) 
    8.         delete pObj; 
    9.       else 
    10.       { 
    11.         std::lock_guard guard(m_mutex); 
    12.         m_lObjects.push_front(std::shared_ptr(pObj, m_fObjDeleter)); 
    13.       } 
    14.     }; 
    15.   } 
    16.  
    17.   ~ObjectPool() 
    18.   { 
    19.     m_bDeconstruct = true
    20.   } 
    21.  
    22.   template 
    23.   std::shared_ptr GetObject(Args&&... args) 
    24.   { 
    25.     std::shared_ptr pObject; 
    26.     { 
    27.       std::lock_guard guard(m_mutex); 
    28.       if (!m_lObjects.empty()) 
    29.       { 
    30.         pObject = m_lObjects.front(); 
    31.         m_lObjects.pop_front(); 
    32.       } 
    33.     } 
    34.  
    35.     if (!pObject) 
    36.     { 
    37.       pObject.reset(new T(std::forward(args)...), m_fObjDeleter); 
    38.     } 
    39.     return pObject; 
    40.   } 
    41.  
    42.   void ReturnObject(std::shared_ptr pObject) 
    43.     if (!pObject) 
    44.       return
    45.  
    46.     std::lock_guard guard(m_mutex); 
    47.     m_lObjects.push_front(pObject); 
    48.   } 
    49.  
    50. private: 
    51.   std::function m_fObjDeleter; 
    52.   std::mutex m_mutex; 
    53.   std::list> m_lObjects; 
    54.   volatile bool m_bDeconstruct = false
    55. }; 

    自动回收

    关于自动回收,这个涉及到一个问题,是用unique_ptr还是shared_ptr呢,在这篇大牛写的文章中进行了比较详细的阐述《thinking in object pool》(链接见参考部分), 说明了应该使用unique_ptr,也看到不少人在网上转发。主要如下阐述:

    因为我们需要把智能指针的默认删除器改为自定义删除器,用shared_ptr会很不方便,因为你无法直接将shared_ptr的删除器修改为自定义删除器,虽然你可以通过重新创建一个新对象,把原对象拷贝过来的做法来实现,但是这样做效率比较低。而unique_ptr由于是独占语义,提供了一种简便的方法方法可以实现修改删除器,所以用unique_ptr是最适合的。

    这种方式需要每次都创建一个新对象,并且拷贝原来的对象,是一种比较低效的做法。

    但本人自己进行了思考,认为可以做到使用shared_ptr一样实现了高效的自动回收机制。首先定义了一个m_fObjDeleter自定义deleter, 不过这种做法可能比较难理解一些,就是定义的m_fObjDeleter函数内也会调用m_fObjDeleter。当shared_ptr引用计数为0的时候,会做如下事情:

    • 如果发现是OjbectPool调用了析构函数,则直接释放对象
    • 如果发现OjbectPool并没有调用析构函数,则将对象放入对象池中
    1. m_fObjDeleter = [&](T* pObj) { 
    2.   if (m_bDeconstruct) 
    3.     delete pObj; 
    4.   else 
    5.   { 
    6.     std::lock_guard guard(m_mutex); 
    7.     m_lObjects.push_front(std::shared_ptr(pObj, m_fObjDeleter)); 
    8.   } 
    9. }; 

    当创建对象的时候指定自定义的deleter:

    1. pObject.reset(new T(std::forward(args)...), m_fObjDeleter); 

    模板支持

    使用了模板可以支持通用的对象:

    1. template 
    2. class ObjectPool 
    3. public
    4.     //...... 
    5.     template 
    6.     std::shared_ptr GetObject(Args&&... args) 
    7.     { 
    8.         //...... 
    9.     } 
    10.  
    11.     void ReturnObject(std::shared_ptr pObject) 
    12.     { 
    13.         //...... 
    14.     } 
    15.  
    16. private: 
    17.     std::function m_fObjDeleter; 
    18.     //..... 
    19.     std::list> m_lObjects; 
    20.     //....... 
    21. }; 

    可变函数参数完美转发

    不同的对象,可能使用的构造函数参数也不同,那么当调用GetObject的时候的参数要设置为可变参数,其实现如下:

    1. template 
    2. std::shared_ptr GetObject(Args&&... args) 
    3.   std::shared_ptr pObject; 
    4.   { 
    5.     std::lock_guard guard(m_mutex); 
    6.     if (!m_lObjects.empty()) 
    7.     { 
    8.       pObject = m_lObjects.front(); 
    9.       m_lObjects.pop_front(); 
    10.     } 
    11.   } 
    12.  
    13.   if (!pObject) 
    14.   { 
    15.     pObject.reset(new T(std::forward(args)...), m_fObjDeleter); 
    16.   } 
    17.   return pObject; 

    其他

    以上对对象池的基本内容进行了阐述,那么对于对象池的实现要根据场景还有若干的细节,有些还比较重要:

    • 是否要在启动的时候初始化指定数量的对象?
    • 对象池的数量是否要设置一个上限或者下线
    • 对象池重复利用,当取出来后要注意,是不是要对对象做一次reset之类的操作,防止对象上一次的调用残留数据对本地调用构成影响,这个要根据自己对象的特点去进行相应的reset操作
    • 有时候当这个对象可能出现了特别的情况需要销毁,是否也需要考虑到?
    • 等等

    参考

    • <>模板部分
    • << thinking in object pool >>: https://www.cnblogs.com/qicosmos/p/4995248.html

     

     

    免责声明:

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

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

    软考中级精品资料免费领

    • 历年真题答案解析
    • 备考技巧名师总结
    • 高频考点精准押题
    • 资料下载
    • 历年真题
    • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

      难度     813人已做
      查看
    • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

      难度     354人已做
      查看
    • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

      难度     318人已做
      查看
    • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

      难度     435人已做
      查看
    • 2024年上半年系统架构设计师考试综合知识真题

      难度     224人已做
      查看

    相关文章

    发现更多好内容
    咦!没有更多了?去看看其它编程学习网 内容吧