前面已经写了一个与mysql数据库相连接的类,连接数据库
现在写一个线程池来增加连接的效率。
这是我自己的学习记录,所以很多函数记录得很详细
首先是.h文件中连接池的声明
#pragma once
#include<string>
#include<queue>
#include<mutex>
#include<atomic>
#include<thread>
#include<condition_variable>
#include<memory>
#include<functional>
#include"Connection.h"
using namespace std;
/*
* 实现连接池功能模块
* mysql
*/
class mySQLConnectionpool {
public:
static mySQLConnectionpool* GetConnectionPool();// 静态方法,不用创建对象,获取连接对象实例
shared_ptr<Connection> getConnection();//给外部的接口,获取一个可用的空闲连接,最后重定义下智能指针的删除器
private:
mySQLConnectionpool();// 构造函数私有化
bool loadConfigFile(); // 从配置文件加载设置
void produceConnectionTask(); //运行在独立的线程中,专门生产新连接
void scanner();//启动一个新的定时线程,扫描多的空闲时间,超过_maxIdleTime的进行回收
string _ip;
unsigned short _port;
string _username;
string _password;
string _dbname; // 连接数据库名称
int _initSize; //初始池内的连接数
int _maxSize; //池内最大的连接数
int _maxIdleTime; //最大的空闲时间
int _connectionTimeOut; //最大连接超时时间
queue<Connection*> _connectionQueue; // 储存mysql连接的队列
mutex _queueMutex; // 维护连接队列的线程安全互斥锁
atomic_int _connectionCnt; //记录所创建的连接的总数量
condition_variable cv; // 设置条件变量,用于连接的生产线程和消费线程的通信
};
懒汉单例模式
首先使用静态方法,让用户只能获得一个连接池的对象。
单例模式的实现
//线程安全的懒汉单例模式
mySQLConnectionpool* mySQLConnectionpool::GetConnectionPool(){
static mySQLConnectionpool pool;//静态局部变量初始化由编译器自动lock and unlock
return &pool;
}
静态局部变量由编译器自动lock,unlock所以这种单例模式是线程安全的
从配置文件中加载配置
在项目文件加下添加一个.ini的配置文件
bool mySQLConnectionpool::loadConfigFile(){
FILE* pf = fopen("mysql.ini", "r");
if (pf == nullptr) {
LOG("mysql.ini file is not exist!");
return false;
}
while (!feof(pf)){
char line[1024] = { 0 };
fgets(line, 1024, pf);
string str = line;
int idx = str.find('=', 0);
if (idx == -1)//无效的配置项
{
continue;
}
int endidx = str.find('\n', idx);
string key = str.substr(0, idx);
string value = str.substr(idx+1, endidx - idx - 1);
if (key == "ip") {
_ip = value;
}
else if (key == "port") {
_port = atoi(value.c_str());
}
else if (key == "username") {
_username = value;
}
else if (key == "password") {
_password = value;
}
else if (key == "dbname") {
_dbname = value;
}
else if (key == "initSize") {
_initSize = atoi(value.c_str());
}
else if (key == "maxSize") {
_maxSize = atoi(value.c_str());
}
else if (key == "maxIdleTime") {
_maxIdleTime = atoi(value.c_str());
}
else if (key == "connectionTimeOut") {
_connectionTimeOut = atoi(value.c_str());
}
}
return true;
}
//fopen的函数原型
FILE * fopen ( const char * filename, const char * mode );
/*"r"read: 为输入操作打开文件,文件必须存在。
"w" write: 为输出操作创建一个空文件,如果文件已存在,则将已有文件内容舍弃,按照空文件对待。
"a" append: 为输出打开文件,输出操作总是再文件末尾追加数据,如果文件不存在,创建新文件。
"r+" read/update: 为更新打开文件(输入和输出),文件必须存在
"w+" write/update: 为输入和输出创建一个空文件,如果文件已存在,则将已有文件内容舍弃,按照空文件对待。
"a+" append/update: 为输出打开文件,输出操作总是再文件末尾追加数据,如果文件不存在,创建新文件。*/
//feof的函数原型
int feof(FILE *stream)
//当设置了与流关联的文件结束标识符时,该函数返回一个非零值,否则返回零。意思就是当文件到了末尾返回非零值,其他时候返回零
//fgets的函数原型
char *fgets(char *str, int n, FILE *stream)
//从指定流stream中读取一行,并存在str所指向的字符串内。当读取完n-1个字符或读取到换行符或到达文件末尾时,停止读取。
//string..find('a',1);后面的数字代表从什么位置开始查找。如果不加,默认从位置0(即第一个字符)开始查找。如果你要查找的字符不是单个字母,用法和查找单个字母一样,它会返回第一个字符的位置。
接下来是构造函数
//构造
mySQLConnectionpool::mySQLConnectionpool() {
if (!loadConfigFile()) {
return;
}
//创建初始数量的连接
for (int i = 0;i < _initSize;i++) {
Connection* p = new Connection();
p->connect(_ip, _port, _username, _password, _dbname);
p->refresh_aliveTime();//刷新一下进入队列的起始时间
_connectionQueue.push(p);//将创建的连接加入连接队列中
_connectionCnt++; //连接计数器++
}
//启动一个新线程,作为连接的生产者
thread producer(std::bind(&mySQLConnectionpool::produceConnectionTask,this));
producer.detach();
//启动一个新的定时线程,扫描多的空闲时间,超过_maxIdleTime的进行回收
thread scanner(std::bind(&mySQLConnectionpool::scanner, this));
scanner.detach();
}
生产者-消费者模式
先来生产者线程的实现
void mySQLConnectionpool:: produceConnectionTask() {
for (;;) {
unique_lock<mutex> lock(_queueMutex);
while (!_connectionQueue.empty()) {
cv.wait(lock); //队列不空,生产线程进入等待状态
}
//连接数量没有到达上限,创建新连接
if (_connectionCnt < _maxSize) {
Connection* p = new Connection();
p->connect(_ip, _port, _username, _password, _dbname);
p->refresh_aliveTime();
_connectionQueue.push(p);
_connectionCnt++;
}
//通知消费者线程可以消费连接了
cv.notify_all();
}
}
可以看到生产者线程判断连接队列是否为空,如果不为空就进入等待并且释放掉互斥锁,当被消费者线程唤醒时判断队列确实空了,就生产连接并通知消费者消费。
线程安全和互斥锁相关的内容在我前面的文章中有写,这里就不详细的介绍了。
下面是消费者线程函数
shared_ptr<Connection> mySQLConnectionpool::getConnection()
{
unique_lock<mutex> lock(_queueMutex);
while (_connectionQueue.empty())
{
if (cv_status::timeout == cv.wait_for(lock, chrono::milliseconds(_connectionTimeOut)))
{
if (_connectionQueue.empty())
{
LOG("获取空闲连接超时。。。。获取连接失败");
return nullptr;
}
}
}
/*智能指针析构时会直接delete,这里需要自定义智能指针释放资源的方式,把连接归还到队列中*/
shared_ptr<Connection> sp(_connectionQueue.front(), [&](Connection* conp) {
//在服务器应用中调用的,一定要考虑线程安全
unique_lock<mutex> lock(_queueMutex);
conp->refresh_aliveTime();
_connectionQueue.push(conp);
});
_connectionQueue.pop();
cv.notify_all(); //消费完连接以后,通知生产者线程检查一下如果队列为空了,赶紧生产连接
return sp;
}
消费者线程从连接队列中取出一个可用连接,如果连接队列已经为空了,则等待唤醒或者是时间超时,如果是等待时间超时唤醒了此线程则说明目前连接队列中已达到最大数量,则连接失败。
注意这里的智能指针使用lambda函数自定义了智能指针的释放操作,作用域结束后该指针会返回连接队列而不是直接delete。
最后是扫描函数的实现将队列中空闲时间过长的连接释放掉
void mySQLConnectionpool::scanner() {
for (;;) {
this_thread::sleep_for(chrono::seconds(_maxIdleTime));
//扫描整个队列,释放多余的连接
unique_lock<mutex> lock(_queueMutex);
while (_connectionCnt > _initSize) {
Connection* p = _connectionQueue.front();
if (p->getAliveTime() >= (_maxIdleTime*1000)) {
_connectionQueue.pop();
_connectionCnt--;
delete p;
}
else {
break;
}
}
}
}