手写数据库连接池---C++11(下)

接上一集

创建连接的生产者线程

mutex _queueMutex; //维护连接队列的线程安全互斥锁

通过返回智能指针来控制连接的释放,当连接出作用域的时候,智能指针自动调用析构函数,此时只要修改析构函数,让他把资源归还给连接池,而不是删除就可以。

.头文件中添加 void produceConnectionTask();//运行在独立的线程中,专门负责生产新连接
源文件中实现

void ConnectionPool::produceConnectionTask()
{
	for (;;)
	{
		unique_lock<mutex> lock(_queueMutex);
		while (!_connectionQue.empty())
		{
			cv.wait(lock);//队列不空,此处生产线程进入等待状态
		}

		//连接数量没有到达上限,继续创建新连接
		if (_connectionCnt <  _maxSize)
		{
			connection* p = new connection();
			p->connect(_ip, _port, _username, _password, _dbname);
			_connectionQue.push(p);
			_connectionCnt++;
		}
		//通知消费者线程可以消费连接了
		cv.notify_all(); 
	}
}

其中 condition_variable cv;//设置条件变量,用于连接生产线程和连接消费线程的通信

生产者逻辑:
连接池非空,生产者就等待。
如果连接池空了,生产者就检查,此时连接数量是否超过了设置的最大连接数
如果没有到达上限,就创建新连接
通知消费者线程可以消费连接

消费连接的消费者线程代码实践

给外部提供接口,从连接池中获取一个可用的空闲连接
头文件添加:shared_ptr< connection> getConnection();
源文件实现:

shared_ptr<connection> ConnectionPool::getConnection()
{
	unique_lock<mutex> lock(_queueMutex);
	while(_connectionQue.empty())//如果队列空了,即没有连接给我使用
	{
		//等待最大空闲时间
		if (cv_status::timeout == cv.wait_for
		(lock, chrono::milliseconds(_connectionTimeout)))
		{
			if (_connectionQue.empty())
			{
				LOG("获取空闲连接超时了...获取连接失败!");
				return nullptr;
			}

		}

		
	}

	/*
	shared_ptr智能指针析构时,会把connection资源delete掉,相当于
	调用connection的析构函数,connection就被close掉了,这里
	需要自定义shared_ptr的释放资源方式,把connection直接归还到queue当中
	*/
	shared_ptr<connection> sp(_connectionQue.front(),
		[&](connection* pcon) {
			//是在服务器应用线程中调用的,所以一定要考虑队列的线程安全操作
			unique_lock<mutex> lock(_queueMutex);
			_connectionQue.push(pcon);
		});
	_connectionQue.pop();
	
	//消费了队列中最后一个connection,就要通知一下生产者连接
	cv.notify_all();

	return sp;
}

不能使用sleep来控制等待最大空闲时间
因为sleep就直接睡那么长时间了,即使有了新连接可以用,也会睡觉。

消费者逻辑:
当队列空时
---- 等待最大空闲时间
---- ---- 如果超时,获取连接失败,返回nullptr
---- ---- 如果在最大空闲时间内获取,检查到非空,则跳出循环
队列不为空
检查没有超过最大连接数量
创建连接,定义使用后资源的放回
通知生产者,我已经消费了一个连接,让生产者看看是否该生产了

最大空闲时间回收,扫描线程代码实践

在connection.h中添加有关时间的操作(应有三处)

	//刷新一下连接的起始空闲时间点
	void refreshAliveTime() { _alivetime = clock(); }
	//返回存活的时间
	clock_t getAliveTime() { return clock() - _alivetime; }
private:
	MYSQL* _conn;
	clock_t _alivetime;//记录进入空闲状态后的起始存活时间

在mysqlconnection.cpp源文件中,所有进入队列的操作中添加有关时间记录操作

ConnectionPool::ConnectionPool()
{
	//加载配置项
	if (!loadConfigFile())
	{
		return;
	}

	//创建初始数量的连接
	for (int i = 0; i < _initSize; ++i)
	{
		connection* p = new connection();
		p->connect(_ip, _port, _username, _password, _dbname);
		p->refreshAliveTime();//刷新一下开始空闲的起始时间
		_connectionQue.push(p);
		_connectionCnt++;
	}
...
shared_ptr<connection> sp(_connectionQue.front(),
	[&](connection* pcon) {
		//是在服务器应用线程中调用的,所以一定要考虑队列的线程安全操作
		unique_lock<mutex> lock(_queueMutex);
		pcon->refreshAliveTime();//刷新空闲时间
		_connectionQue.push(pcon);
	});
_connectionQue.pop();

定时扫描


//扫描超过maxIdleTime时间的空闲连接,进行多余的连接回收
void  ConnectionPool::scannerConnectionTask()
{
	for (;;)//一直扫描不停止
	{
		// 通过sleep模拟定时效果
		this_thread::sleep_for(chrono::seconds(_maxIdleTime));

		//扫描整个队列,释放多余的连接
		unique_lock<mutex> lock(_queueMutex);
		while (_connectionCnt > _initSize)
		{
			connection* p = _connectionQue.front();
			if (p->getAliveTime() > _maxIdleTime * 1000)//单位ms
			{
				_connectionQue.pop();
				_connectionCnt--;
				delete p;//调用~connection()释放连接
			}
			else
			{
				//  队头的连接没有超过_maxIdleTime,其他的肯定也没超过
				break;
			}
		}

	}
}

主要是在进入队列时,记录进入队列的时间
定时的扫描队列
当队头元素没超时时,其他元素肯定也没超时
当队头元素超时时,陆续释放超时资源,直到没有,或者已经到连接池初始大小。

压力测试

不使用连接池 — 单线程

在main函数中实现,添加1000条数据库数据,记录时间。

#include<iostream>
using namespace std;
#include"connection.h"
#include"mysqlconnection.h"
int main()
{
	clock_t begin = clock();

	for (int i = 0; i < 1000; ++i)
	{
		connection conn;
		char sql[1024] = { 0 };
		sprintf(sql, "insert into user(name,age,sex) values('%s',%d,'%s')",
			"zhang san", 20, "male");
		conn.connect("127.0.0.1", 3306, "root", "jiabei880", "tc_connection");
		conn.update(sql);
	}

	clock_t end = clock();

	cout << end - begin << "ms" << endl;
	return 0;

}

8100ms
在这里插入图片描述
在mysql中把数据删除
在这里插入图片描述
确实插入了1000行
同理测试5000行,10000行

使用连接池 — 单线程
clock_t begin = clock();


for (int i = 0; i < 10000; ++i)
{
	/*connection conn;
	char sql[1024] = { 0 };
	sprintf(sql, "insert into user(name,age,sex) values('%s',%d,'%s')",
		"zhang san", 20, "male");
	conn.connect("127.0.0.1", 3306, "root", "jiabei880", "tc_connection");
	conn.update(sql);*/

	ConnectionPool* cp = ConnectionPool::getConnectionPool();
	shared_ptr<connection>sp = cp->getConnection();
	char sql[1024] = { 0 };
	sprintf(sql, "insert into user(name,age,sex) values('%s',%d,'%s')",
		"zhang san", 20, "male");
	sp->update(sql);


}

clock_t end = clock();

cout << end - begin << "ms" << endl;

得到结果,将近是不使用连接池的一半
在这里插入图片描述

不使用连接池 — 多线程
	clock_t begin = clock();

	thread t1([]() {
		//ConnectionPool* cp = ConnectionPool::getConnectionPool();
		for (int i = 0; i < 1250; ++i)
		{
			connection conn;
			char sql[1024] = { 0 };
			sprintf(sql, "insert into user(name,age,sex) values('%s',%d,'%s')",
				"zhang san", 20, "male");
			conn.connect("127.0.0.1", 3306, "root", "jiabei880", "tc_connection");
			conn.update(sql);
		}	
		});


	thread t2([]() {
		//ConnectionPool* cp = ConnectionPool::getConnectionPool();
		for (int i = 0; i < 1250; ++i)
		{
			connection conn;
			char sql[1024] = { 0 };
			sprintf(sql, "insert into user(name,age,sex) values('%s',%d,'%s')",
				"zhang san", 20, "male");
			conn.connect("127.0.0.1", 3306, "root", "jiabei880", "tc_connection");
			conn.update(sql);
		}
		});

	thread t3([]() {
		//ConnectionPool* cp = ConnectionPool::getConnectionPool();

		for (int i = 0; i < 1250; ++i)
		{
			connection conn;
			char sql[1024] = { 0 };
			sprintf(sql, "insert into user(name,age,sex) values('%s',%d,'%s')",
				"zhang san", 20, "male");
			conn.connect("127.0.0.1", 3306, "root", "jiabei880", "tc_connection");
			conn.update(sql);
		}
		});

	thread t4([]() {
	//	ConnectionPool* cp = ConnectionPool::getConnectionPool();

		for (int i = 0; i < 1250; ++i)
		{
			connection conn;
			char sql[1024] = { 0 };
			sprintf(sql, "insert into user(name,age,sex) values('%s',%d,'%s')",
				"zhang san", 20, "male");
			conn.connect("127.0.0.1", 3306, "root", "jiabei880", "tc_connection");
			conn.update(sql);
		}
		});


	t1.join();
	t2.join();
	t3.join();
	t4.join();
	

结果:在这里插入图片描述

使用连接池 — 多线程

我们创建四个线程来测试1000组数据

clock_t begin = clock();

thread t1([]() {
	ConnectionPool* cp = ConnectionPool::getConnectionPool();

	for (int i = 0; i < 250; ++i)
	{
		char sql[1024] = { 0 };
		sprintf(sql, "insert into user(name,age,sex) values('%s',%d,'%s')",
		"zhang san", 20, "male");
		shared_ptr<connection>sp = cp->getConnection();
		sp->update(sql);
	}	
	});


thread t2([]() {
	ConnectionPool* cp = ConnectionPool::getConnectionPool();

	for (int i = 0; i < 250; ++i)
	{
		char sql[1024] = { 0 };
		sprintf(sql, "insert into user(name,age,sex) values('%s',%d,'%s')",
			"zhang san", 20, "male");
		shared_ptr<connection>sp = cp->getConnection();
		sp->update(sql);
	}
	});

thread t3([]() {
	ConnectionPool* cp = ConnectionPool::getConnectionPool();

	for (int i = 0; i < 250; ++i)
	{
		char sql[1024] = { 0 };
		sprintf(sql, "insert into user(name,age,sex) values('%s',%d,'%s')",
			"zhang san", 20, "male");
		shared_ptr<connection>sp = cp->getConnection();
		sp->update(sql);
	}
	});

thread t4([]() {
	ConnectionPool* cp = ConnectionPool::getConnectionPool();

	for (int i = 0; i < 250; ++i)
	{
		char sql[1024] = { 0 };
		sprintf(sql, "insert into user(name,age,sex) values('%s',%d,'%s')",
			"zhang san", 20, "male");
		shared_ptr<connection>sp = cp->getConnection();
		sp->update(sql);
	}
	});


t1.join();
t2.join();
t3.join();
t4.join();

clock_t end = clock();
cout << end - begin << "ms" << endl;
return 0;

得到结果
在这里插入图片描述

同理测试5000条和10000条数据

结果汇总
数据量未使用连接池花费时间使用连接池花费时间
1000单线程:8100ms 四线程 :2301ms单线程:3900ms 四线程:1351ms
5000单线程:40428ms 四线程 :11387ms单线程:19372ms 四线程 :5992ms
10000单线程:82463ms 四线程 :23229ms单线程:39469ms 四线程 :12133ms

注: 表中结果均为清空数据库后,连续插入三次取平均值所得

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值