创建连接的生产者线程
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 |
注: 表中结果均为清空数据库后,连续插入三次取平均值所得
1423

被折叠的 条评论
为什么被折叠?



