有两个类,MySQL 和 ConnectionPool。MySQL负责封装mysql提供的接口,ConnectionPool负责管理连接。
1、两个类
MySQL类如下,这个还好,就是对原api的封装:
// 数据库操作类
class MySQL
{
public:
//初始化数据库连接
MySQL();
//释放数据库连接资源
~MySQL();
//连接数据库
bool connect();
// 更新操作
bool update(string sql);
// 查询操作
MYSQL_RES *query(string sql);
// 获取连接
MYSQL* getConnection();
//刷新一下连接的起始的空闲时间点
void refreshAliveTime() { _alivetime = clock(); }
//返回存活的时间
clock_t getAliveTime() const { return clock() - _alivetime; }
private:
MYSQL *_conn;
clock_t _alivetime; //记录进入空闲状态后的起始存活时间
// mutex _dbConnMutex;
};
ConnectionPool类如下:
class ConnectionPool
{
public:
//获取连接池对象实例
static ConnectionPool* getConnectionPool();
//给外部提供接口, 从连接池中获取一个可用的空闲连接
//用智能指针管理外部的释放工作
shared_ptr<MySQL> getConnection();
private:
//单例#1 构造函数私有化
ConnectionPool();
//从配置文件中加载配置项
bool loadConfigFile();
//运行在独立的线程中,专门负责生产新连接A
void produceConnectionTask();
//扫描超过maxIdleTime时间的空闲连接, 进行对应的连接回收
void scannerConnectionTask();
string _ip; //mysql的ip地址
unsigned short _port; //mysql的端口号 3306
string _username; //mysql登录用户名
string _dbname; //mysql的名称
string _passward; //登录密码
int _initSize; //连接池的初始连接量
int _maxSize; //连接池的最大连接量
int _maxIdleTime; //连接池最大空闲时间
int _connectionTimeOut; //连接池获取连接的超时时间
queue<MySQL*> _connectionQue; //存储mysql连接的队列
mutex _queueMutex; //维护连接队列的线程互斥安全锁
atomic_int _connectionCnt; //记录所创建的connection连接的总数量
condition_variable cv; //设置条件变量, 用于连接生产者线程和消费者线程的通信
};
2、static实现线程安全的单例模式
// 声明为
static ConnectionPool* getConnectionPool();
// 实现为
ConnectionPool* ConnectionPool::getConnectionPool()
{
static ConnectionPool pool; //lock和unlock
return &pool;
}
我认为静态和非静态最根本的区别是调用时前面没有 this指针。
static 有以下几种常见的用法:
1、静态成员变量
在类中声明的静态成员变量属于类本身,而不是类的实例。它在类的所有实例之间共享,并且在类的生命周期内保持不变。很常见的例子就是计数器。
2、静态成员函数
静态成员函数不属于类的实例,而是类本身的函数。它们可以直接通过类名访问,而无需创建类的实例。
3、静态变量
静态局部变量在函数第一次被调用时初始化,并且在函数的多次调用之间保留其值。它们在函数的生命周期内保持不变,并且具有静态存储期限。
静态全局变量是在全局作用域(文件范围内)声明的变量,但其作用域被限制在声明它的文件中,不可被其他文件访问。静态全局变量在整个程序运行期间保持其值,只在声明的文件内可见。
4、静态函数
静态函数只在定义所属的文件中可见,其他文件无法调用该函数。它们被限制在定义所在的文件中使用,用于实现文件范围的功能。
函数嵌套定义的话得是lambda表达式
#include <iostream>
int main() {
// 定义一个 lambda 表达式
static auto lambda = []() {
std::cout << "This is a lambda function." << std::endl;
};
// 调用 lambda 表达式
lambda();
return 0;
}
5、命名空间中的静态变量
在命名空间中声明的静态变量只在声明所在的文件中可见,其他文件无法访问。它们可以用于实现在给定文件中全局可见但在其他文件中不可见的数据。
6、静态断言
static_assert是一种用于在编译时进行断言检查的关键字。它在编译期间对指定的条件进行评估,并在条件为假时引发编译错误,用于进行编译时的静态检查。
#include <iostream>
#include <cassert>
template <typename T>
void processValue(T value) {
// 使用静态断言检查是否是整数类型
static_assert(std::is_integral<T>::value, "Value must be an integer type.");
// 执行某些处理...
std::cout << "Processing value: " << value << std::endl;
}
int main() {
int intValue = 42;
double doubleValue = 3.14;
processValue(intValue); // 合法的调用
processValue(doubleValue); // 编译时错误,不满足静态断言条件
return 0;
}
3、管理连接
使用队列管理连接。因为是并发的,上锁很有必要。初始化时会有两个线程,一个是生产者线程、一个是回收线程。
生产者进程在队列不为空时进入等待状态,那个队列为空且数量没有达到上限时继续创建新的连接。
获取连接的接口在队列为空时会等待timeout时间,该时间内只要收到了消息就ok,当有可用连接时将队头的连接给出去,如果队列为空了,通知生产者(不为空时生产者处于wait状态)。这里要自定义下shared_ptr的释放方式:当shared_ptr不再引用对象时,不释放连接、刷新下时间后再次入队列。
// shared_ptr<MySQL> sp(_connectionQue.front(), ...):这里创建了一个shared_ptr对象sp来管理_connectionQue队列的首个元素
// [&](MySQL *pcon) {...} 自定义删除器
shared_ptr<MySQL> sp(_connectionQue.front(),
[&](MySQL *pcon) {
//要考虑队列的线程安全 这里的抢锁发生在出作用域析构,不是现在
unique_lock<mutex> lock(_queueMutex);
pcon->refreshAliveTime();
_connectionQue.push(pcon);
}
;
4、回收
每隔一段时间扫描一遍。将超过初始长度并且超时的连接释放。(获得连接那里是自定义释放,这里直接delete)
创建连接的时候刷新时间,连接池里存放的是没有被使用的空闲连接。
我找到了一个bug!!扫描队列那里,是有可能出现空的情况!这时需要判断下队列是否为空再进行下一步。
//扫描超过maxIdleTime时间的空闲连接, 进行对应的连接回收
void ConnectionPool::scannerConnectionTask()
{
for (;;)
{
//通过sleep模拟定时效果
this_thread::sleep_for(chrono::seconds(_maxIdleTime));
cout << "scannerConnectionTask" << endl;
//扫描整个队列, 释放多余的连接
unique_lock<mutex> lock(_queueMutex);
while (_connectionCnt > _initSize) // 我觉得这里要判断下队列是否为空 因为线程池里面放的是没有被用的连接
{
if (_connectionQue.size())
{
MySQL* p = _connectionQue.front();
cout << p->getAliveTime() << endl;
if (p->getAliveTime() >= _maxIdleTime * 1000)
{
_connectionQue.pop();
_connectionCnt--;
delete p; //释放连接 调用~Connection()
}
else
{
break; //队头的连接没有超过_maxIdleTime, 其他的肯定没有
}
}
else break;
}
}
}