文章目录
1. 池化技术
1.1 简介
-
提前保存大量的资源,以备不时之需以及重复使用,池化技术应用广泛,如内存池,线程池,连接池(网络链接池,数据库链接池)等等。
-
由于在实际应用当做分配内存、创建进程、线程都会设计到一些系统调用,系统调用需要导致程序从用户态切换到内核态,是非常耗时的操作。因此,当程序中需要频繁的进行内存申请释放,进程、线程创建销毁等操作时,通常会使用内存池、进程池、线程池技术来提升程序的性能。
-
池化对连接或线程的复用,并对复用的数量、时间等进行控制,从而使得系统的性能和资源消耗达到最优状态。
1.2 使用池化结构的优势
-
在衔接节点上,无池结构时需要不断的创建和销毁新服务对象必将给造成系统资源的巨大开销,导致系统的性能下降,甚至系统资源耗尽。
-
使用池结构可以减少不必要的新服务对象的创建和销毁,用一个容器保存着各种需要的对象。对这些对象进行复用,从而降低系统资源开销、提高程序的响应速度、改善效率。
-
本质上,池是在资源使用时将使用率高且可以复用的对象保存在一个类似容器的地方,对这些对象进行复用,从而降低非必须的系统资源开销、提高程序的响应速度、改善效率。
1.3 常用的池技术
- 对象池
- 内存池
- Socket连接池
- 数据库连接池
- 线程池
2. 对象池
对象池技术是一种常见的对象缓存手段。“对象”意味着池中的内容是一种结构化实体,这也就是一般意义上面向对象中的对象模型;“池”(或动词池化)意味着将有生命周期的对象缓存到"池子"中进行管理,即用即取。缓存的目的大多是为了提升性能,对象池技术的目的也即如此。所以,对象池技术的本质简单来说就是:将具有生命周期的结构化对象缓存到带有一定管理功能的容器中,以提高对象的访问性能。
处理网络连接是对象池使用最多的场景。比如一些RPC框架的NettyChannel缓存(如motan),以及数据库连接池的Connection缓存(如DBCP)等。除此之外,我们还可以利用对象池管理一些大对象,这些对象有着相对复杂的构造过程。
2.1 对象池技术与普通的本地缓存(比如guava cache)有什么区别?
- 其一,本地cache可能会有一些失效策略,比如按照时间、访问次数等,而对象池是可以没有这些特性的;
- 其二,也是最重要的一点,缓存中的对象是没有一个完整生命周期的概念,而对象池中的对象是具有生命周期的,我们甚至可以对对象的生命周期施加影响。
2.2 版本1
2.2.1 需求
可以取得和归还池对象,池对象组织形式为先进先出(FIFO),队列中池对象的个数要有上限,可以管理不同类型的池对象。
2.2.2 设计
对象池类设计为模板类型,使用Dqueue存储池对象,提供borrowObject(),和returnObject()两个接口函数。
2.2.3 代码示例
代码示例
#include<iostream>
#include<string>
#include<vector>
#include<list>
#include<map>
#include<deque>
using namespace std;
class Point
{
int _x;
int _y;
public:
Point() :_x(0), _y(0)
{
cout << " Create Point " << endl;
}
Point(int x, int y) :_x(x), _y(y)
{
cout << " Create Point(int,int) " << endl;
}
~Point()
{
cout << " destroy Point " << endl;
}
void Print() const
{
cout << "X: " << _x << "Y: " << _y << endl;
}
void SetX(int x)
{
_x = x;
}
void SetY(int y)
{
_y = y;
}
int GetX() const
{
return _x;
}
int GetY() const
{
return _y;
}
};
//日志文件:输出到屏幕上
#define LOG(info)\
cout <<__FILE__<< ":" <<__LINE__<< " "\
<< __TIMESTAMP__ << ":" << info << endl;
//对象池
const int MaxObjectNum = 10;
template<class T>
class ObjectPool
{
private:
bool needClose;//是否关闭对象的标记(初始化为false)
std::deque<T*> m_object_queue;//双端队列
public:
ObjectPool() :needClose(false) {}
ObjectPool(const ObjectPool&) = delete;//删除拷贝构造函数
ObjectPool& operator=(const ObjectPool&) = delete;//删除赋值函数
~ObjectPool()
{
Clear();
}
//关闭对象池
void Close()
{
needClose = true;
Clear();
}
//清空内容
void Clear()
{
for (auto x : m_object_queue)
{
delete x;
}
}
//返回空闲对象个数
size_t GetNumIdle() const
{
return m_object_queue.size();
}
void Init(size_t num)
{
if (needClose)
{
LOG("对象池已经关闭");
exit(1);
}
if (num <= 0 || num > MaxObjectNum)
{
LOG("创建对象个数错误");
return;
}
for (size_t i = 0; i < num; i++)
{
m_object_queue.push_back(new T());
}
}
public:
//借出对象
T* borrowObject()
{
if (needClose)
{
LOG("对象池已经关闭");
//1.exit(1);2.return nulllptr;
return nullptr;
}
if (m_object_queue.empty())//对象为空,无对象可借
{
return nullptr;
}
T* tmp = m_object_queue.front();//取头部数据
m_object_queue.pop_front();
return tmp;
}
//返还对象
void returnObject(T* p)
{
if (nullptr == p)
{
LOG("池对象丢失/对象为空");
return;
}
m_object_queue.push_back(p);
return;
}
};
int main()
{
ObjectPool<Point> objpool;
objpool.Init(5);
Point* p1 = objpool.borrowObject();
Point* p2 = objpool.borrowObject();
objpool.returnObject(p1);
objpool.returnObject(p2);
p1 = new Point();
objpool.returnObject(p1);
return 0;
}
运行结果
2.2.4 出现问题
- 对象池中的对象没有到达上限,无法增加对象;
- 丢失的对象会造成内存泄漏;
- 不知道借出对象的个数和空闲对象的个数。
2.3 版本2
2.3.1 设计
- 对象池类型中设计两个队列,一个是空闲队列,一个是活动队列;
- 设计一个私有方法AddObject()。
2.3.2 代码示例
代码示例
#include<iostream>
#include<string>
#include<vector>
#include<list>
#include<map>
#include<deque>
#include<algorithm>
using namespace std;
class Point
{
int _x;
int _y;
public:
Point() :_x(0), _y(0)
{
cout << " Create Point " << endl;
}
Point(int x, int y) :_x(x), _y(y)
{
cout << " Create Point(int,int) " << endl;
}
~Point()
{
cout << " destroy Point " << endl;
}
void Print() const
{
cout << "X: " << _x << "Y: " << _y << endl;
}
void SetX(int x)
{
_x = x;
}
void SetY(int y)
{
_y = y;
}
int GetX() const
{
return _x;
}
int GetY() const
{
return _y;
}
};
//日志文件:输出到屏幕上
#define LOG(info)\
cout <<__FILE__<< ":" <<__LINE__<< " "\
<< __TIMESTAMP__ << ":" << info << endl;
//对象池
const int MaxObjectNum = 10;
template<class T>
class ObjectPool
{
private:
bool needClose;//是否关闭对象的标记(初始化为false)
size_t createNum;//创建对象个数
std::deque<T*> m_idle_object_queue;//空闲队列
std::deque<T*> m_active_object_queue;//活动队列
public:
ObjectPool() :needClose(false), createNum(0) {}
ObjectPool(const ObjectPool&) = delete;//删除拷贝构造函数
ObjectPool& operator=(const ObjectPool&) = delete;//删除赋值函数
~ObjectPool()
{
Clear();
}
//关闭对象池
void Close()
{
needClose = true;
Clear();
}
//清空内容
void Clear()
{
for (auto x : m_idle_object_queue)
{
delete x;
}
m_idle_object_queue.clear();
for (auto x : m_active_object_queue)
{
delete x;
}
m_active_object_queue.size();
}
//获得活动队列个数
size_t GetNumActive() const
{
return m_active_object_queue.size();
}
//返回空闲对象个数
size_t GetNumIdle() const
{
return m_idle_object_queue.size();
}
void Init(size_t num)
{
if (needClose)
{
LOG("对象池已经关闭");
exit(1);
}
if (num <= 0 || num > MaxObjectNum)
{
LOG("创建对象个数错误");
return;
}
if (createNum + num > MaxObjectNum)
{
LOG("已达到创建对象个数上限");
return;
}
//创建对象个数
createNum += num;
for (size_t i = 0; i < num; i++)
{
m_idle_object_queue.push_back(new T());
}
}
public:
//添加对象
void AddObject(size_t num)
{
Init(num);
}
//借出对象
T* borrowObject()
{
if (needClose)//对象已关闭
{
LOG("对象池已经关闭");
//1.exit(1);2.return nulllptr;
return nullptr;
}
if (m_idle_object_queue.empty())//对象为空,无对象可借
{
LOG("对象池无对象");
if (createNum >= MaxObjectNum)
{
return nullptr;
}
AddObject(1);
}
T* tmp = m_idle_object_queue.front();//取头部数据
m_idle_object_queue.pop_front();
m_active_object_queue.push_back(tmp);//添加到活动池
return tmp;
}
//回收对象
void returnObject(T* p)
{
if (nullptr == p)
{
LOG("池对象丢失/对象为空");
return;
}
m_idle_object_queue.push_back(p);
std::erase(m_active_object_queue, p);
return;
}
};
int main()
{
Point* par[10] = {};
ObjectPool<Point> objpool;
objpool.Init(5);
for (int i = 0; i < 10; ++i)
{
par[i] = objpool.borrowObject();
}
return 0;
}
改良函数代码
void returnObject(T* p)
{
if (nullptr == p)
{
LOG("池对象丢失/对象为空");
return;
}
if (std::erase(m_active_object_queue, p))
{
m_idle_object_queue.push_back(p);
LOG("对象已归还到对象池");
}
else
{
LOG("非此对象池对象,无需归还");
}
return;
}
运行结果
代码图解
2.3.3 出现问题
- 对象如果没有归还给对象池,对象无法销毁。
- 对象如果在对象池外释放,对象池无法感知。
出现裸指针
2.4 版本3
2.4.1 设计
-
通过自动回收用完的对象来解决。这里用智能指针就可以解决,在创建智能指针时可以指定删除器,在删除器中不删除对象,而是将其回收到对象池中。这个过程对外界来说是看不见的,由智能指针自己完成。
-
使用C11的智能指针管理对象的生存期,重写删除器,使删除器与对象池结合;
2.4.2 代码示例
代码示例
#include<iostream>
#include<string>
#include<vector>
#include<list>
#include<map>
#include<deque>
#include<algorithm>
using namespace std;
class Point
{
int _x;
int _y;
public:
Point() :_x(0), _y(0)
{
cout << " Create Point " << endl;
}
Point(int x, int y) :_x(x), _y(y)
{
cout << " Create Point(int,int) " << endl;
}
~Point()
{
cout << " destroy Point " << endl;
}
void Print() const
{
cout << "X: " << _x << "Y: " << _y << endl;
}
void SetX(int x)
{
_x = x;
}
void SetY(int y)
{
_y = y;
}
int GetX() const
{
return _x;
}
int GetY() const
{
return _y;
}
};
//日志文件:输出到屏幕上
#define LOG(info)\
cout <<__FILE__<< ":" <<__LINE__<< " "\
<< __TIMESTAMP__ << ":" << info << endl;
//对象池
const int MaxObjectNum = 10;
template<class T>
class ObjectPool
{
private:
//删除器的编写
class ObjectDeleter
{
ObjectPool* _pool;
public:
ObjectDeleter(ObjectPool* p) :_pool(p) {}
void operator() (T* pobj)//调动仿函数
{
if (_pool->needClose)//关闭内存池
{
LOG("对象已释放");
delete pobj;
}
else
{
LOG("对象已归还");
_pool->activeNum -= 1;
_pool->m_idle_object_queue.push_back(std::shared_ptr<T> (pobj));
}
}
};
private:
bool needClose;//是否关闭对象的标记(初始化为false)
size_t createNum;//创建对象个数
size_t activeNum;//活动对象个数
std::deque<shared_ptr<T>> m_idle_object_queue;//空闲对象
std::deque<shared_ptr<T>> m_active_object_queue;//活动对象
public:
ObjectPool() :needClose(false), createNum(0), activeNum(0) {}
ObjectPool(const ObjectPool&) = delete;//删除拷贝构造函数
ObjectPool& operator=(const ObjectPool&) = delete;//删除赋值函数
~ObjectPool()
{
Clear();
}
//关闭对象池
void Close()
{
needClose = true;
Clear();
}
//清空内容
void Clear()
{
m_idle_object_queue.clear();
}
//获得活动队列个数
size_t GetNumActive() const
{
return activeNum;
}
//返回空闲对象个数
size_t GetNumIdle() const
{
return m_idle_object_queue.size();
}
void Init(size_t num)
{
if (needClose)
{
LOG("对象池已经关闭");
exit(1);
}
if (num <= 0 || num > MaxObjectNum)
{
LOG("创建对象个数错误");
return;
}
if (createNum + num > MaxObjectNum)
{
LOG("已达到创建对象个数上限");
return;
}
//创建对象个数
createNum += num;
for (size_t i = 0; i < num; i++)
{
m_idle_object_queue.push_back(std::shared_ptr<T>(new T(), ObjectDeleter(this)));
}
}
public:
//添加对象
void AddObject(size_t num)
{
Init(num);
}
//借出对象
shared_ptr<T> borrowObject()
{
if (needClose)//对象已关闭
{
LOG("对象池已经关闭");
//1.exit(1);2.return nulllptr;
return nullptr;
}
if (m_idle_object_queue.empty())//对象为空,无对象可借
{
LOG("对象池无对象");
if (createNum >= MaxObjectNum)
{
return nullptr;
}
AddObject(1);
}
shared_ptr<T> tmp = m_idle_object_queue.front();//取头部数据
m_idle_object_queue.pop_front();
activeNum += 1;
return tmp;
}
//回收对象
void returnObject(shared_ptr<T> p)
{
if (nullptr == p)
{
LOG("池对象丢失/对象为空");
return;
}
/*m_idle_object_queue.push_back(p);
activeNum -= 1;
LOG("对象已归还到对象池");*/
p.reset();
return;
}
};
int main()
{
ObjectPool<Point> objpool;
objpool.Init(5);
shared_ptr<Point> ps1 = objpool.borrowObject();
shared_ptr<Point> ps2 = objpool.borrowObject();
shared_ptr<Point> ps3 = objpool.borrowObject();
objpool.returnObject(ps1);
ps1 = objpool.borrowObject();
ps2.reset();
ps3.reset();
ps1 = objpool.borrowObject();
return 0;
}
运行结果
代码图解
当对象再次被借出并且归还时就不能归还到对象池内。因为再次借出时候系统会调用默认删除器,默认删除器没有将对象再次归还到对象池内的功能。
3. 完美转发
3.1 定义
-
完美转发(perfect forwarding)问题是指函数模板在向其他函数传递参数时该如何保留该参数的左右值属性的问题。
-
也就是说函数模板在向其他函数传递自身形参时,如果相应实参是左值,它就应该被转发为左值;同样如果相应实参是右值,它就应该被转发为右值。
-
这样做是为了保留在其他函数针对转发而来的参数的左右值属性进行不同处理(比如参数为左值时实施拷贝语义;参数为右值时实施移动语义)的可能性。
-
如果将自身参数不分左右值一律转发为左值,其他函数就只能将转发而来的参数视为左值,从而失去针对该参数的左右值属性进行不同处理的可能性。
不完美转发
template<typename T>
void function(T t)//非引用,形参会对实参进行拷贝。
{
//对于函数内部来说,t有自己的名称,也可以获取它的存储地址,因此它永远都是左值。
otherdef(t);
}
完美转发
-
C++11 标准中规定,通常情况下右值引用形式的参数只能接收右值,不能接收左值。
-
但对于函数模板中使用右值引用语法定义的参数来说,它不再遵守这一规定,既可以接收右值,也可以接收左值(此时的右值引用又被称为“万能引用”)。
template<typename T>
void function(T &&t)//既可以接受左值,又可以接受右值
{
otherdef(t);// t继续传参,在otherdef()中又变成了左值
}
- 在实现完美转发时,只要函数模板的参数类型为 T&&,则 C++ 可以自行准确地判定出实际传入的实参是左值还是右值。
4. RTTI
- RTTI:运行时类型的识别。
- 需要两个类型作为支持:typeid,动态类型转化
- 特点:编译时期将类型名,类里面的成员函数,类的属性等等保存起来形成数据库,保存在.exe文件中。当.exe文件运行时,将类的信息加载到程序中,程序运行时可以获得当前类型信息。
- 缺点:内存获取的代价太大。
5. 问题
讲解了一个通用对象池实现上可能需要考虑的方方面面。在阅读本文之前,我们先思考以下几个问题:
1.进入池子的对象是什么?需要包装一下吗?
答:需要
2.池子中的对象可能有一些什么状态和属性呢?
答:状态:空闲,活动,驱离,超时。
3.池子中的对象可能有哪些组织形式(数据结构)?
答:队列(双端队列:先进先出/后进后出)组织,map表组织。
4.池子中的对象是怎么创建出来的?我们如何控制池子中的对象(生命周期)?
答:智能指针可以控制对象的生存周期,使用可变模板参数进行对象的创建,或者将对象写入配置文件,如果要创建对象,就读取配置文件,根据配置文件信息创建对象。(使用json创建配置文件,可以传输文本文件,也可以传输二进制文件)
5.什么时候我们需要校验一个池对象的有效性?
答:池对象的有效性:将一个数据库链接池传送给用户,用户用selete进行查询,由于对象池过大导致池对象无法归还。
6.什么是eviction(驱逐)?什么是abandon(放弃)?区别是什么?如何实现?
答:如何将问题5中的对象进行驱逐和放弃?
计时器和定时器实现。
7.有哪些对象池接口方法可以提供给用户使用呢?
8.对象被借到的顺序是怎么样的? fifo or lifo?
9.池子中需要定时任务么?定时任务能够做什么?