【WebServer项目:https://github.com/Hansimov/linux-server】Day03 - 数据库连接池

CGLmysql --数据库连接池

代码

sql_connection_pool.h文件

    #ifndef _CONNECTION_POOL_
	#define _CONNECTION_POOL_
	
	#include <mysql/mysql.h>
	#include"./lock/locker.h"
	#include"./log/log.h"
	#include<string>
	#include<list>
	
	class connection_pool{
	
	private: //连接池:数据结构、容量、使用情况
	    int m_MaxConn;
	    int m_CurConn;  //当前已使用的连接数
	    int m_FreeConn; //空闲的连接数
	    locker lock;
	    sem reserve;
	    list<MYSQL*> connList; // 连接池
	
	public: //数据连接需要的基本配置
	    string m_url; // 主机地址
	    string m_Port; //端口号
	    string m_User; 
	    string m_Password;
	    string m_DataBaseName;
	    int m_close_log; //日志开关
	
	//单例模式
	private: 
	    connection_pool();
	    ~connection_pool();
	
	public:
	    static connection_pool* GetInstance();
	
	    void init(string url, string User, string Password, string DataBaseName, int Port, int MaxConn, int close_log);
	    MYSQL* GetConnection(); //获取一个连接
	    bool ReleaseConnection(MYSQL* conn); //释放一个连接
	    int GetFreeConn(); //查看空连接数量
	    void DestroyPool(); //销毁连接池
	};
	/*
	    C++面向对象编程时,创建变量会执行构造函数,销毁对象时执行析构函数,若进程堆资源在构造函数中获取,在析构函数中释放资源,
	    即实现了RAII,理论上该技术可以以用于任何的系统资源。
	    
	    //具体四个步骤
	    a.设计一个类封装资源
	
	    b.在构造函数中初始化
	
	    c.在析构函数中执行销毁操作
	
	    d.使用时声明一个该对象的类
	*/
	
	//这个类的目的是,程序员通过它可以获取一个连接池,而且,用完之后,会自动释放
	class connectionRAII{
	private:
	    MYSQL* connRAII;
	    connection_pool *poolRAII; //包含关系,connectionRAI has a connection_pool
	
	public:
	    connectionRAII(MYSQL **con, connection_pool* connPool);
	    ~connectionRAII();
	};
	
	#endif

sql_connection_poll.cpp文件

    #include"sql_connection_pool.h"
	#include<stdarg.h>
	
	//先实现onnection_pool里面的函数
	
	//构造函数
	connection_pool::connection_pool(){
	    m_CurConn = 0;
	    m_FreeConn = 0;
	}
	
	//析构函数
	connection_pool::~connection_pool(){
	   DestroyPool();
	}
	
	//获取唯一实例
	connection_pool* connection_pool::GetInstance(){
	    static connection_pool connPool; //会调用一次构造函数
	    return &connPool;
	}
	
	//初始化线程池
	void connection_pool::init(string url, string User, string Password, string DataBaseName, 
	                            int Port, int MaxConn, int close_log){
	                                                        
	    m_url = url;
	    m_User = User;
	    m_Password = Password;
	    m_DataBaseName = DataBaseName;
	    m_Port = Port;
	    m_MaxConn = MaxConn;
	    m_close_log = close_log;
	
	    //创建MaxConn个连接
	    for(int i = 0; i < m_MaxConn; i++){
	        MYSQL* con = NULL;
	        con = mysql_init(con); //获取或初始化MYSQL结构。
	        if(con == NULL){
	            LOG_ERROR("MySQL mysql_init() error");
	            exit(1);
	        }
	
	        con = mysql_real_connect(con,url.c_str(),User.c_str(),Password.c_str(),DataBaseName.c_str(),Port,NULL,0);
	
	        if(con == NULL){
	            LOG_ERROR("MySQL mysql_real_connect() error");
	            exit(1);
	        }
	        connList.push_back(con);
	        m_FreeConn++;
	    }
	
	    reserve = sem(m_FreeConn);
	    m_MaxConn = m_FreeConn;
	}
	
	//当有请求,从数据库连接池中返回一个可用连接,更新使用和空闲的连接数
	MYSQL* connection_pool::GetConnection(){
	    //先判断池子里面还有没有空闲的连接
	    if(connList.size() == 0){
	        return NULL;
	    }
	    //池子空闲连接减一
	    reserve.wait();
	
	    //获取空闲连接,修改连接池状态数据
	    lock.lock();
	    
	    MYSQL* con = connList.front();
	
	    m_FreeConn--;
	    m_CurConn++;
	
	    lock.unlock();
	
	    return con;
	}
	
	//释放当前使用的连接--把当前连接丢回连接池里面
	bool connection_pool::ReleaseConnection(MYSQL* con){
	    if(con == NULL){
	        return false;
	    }
	
	    lock.lock();
	    connList.push_back(con);
	    m_FreeConn++;
	    m_CurConn--;
	    reserve.post(); //别忘了信号量也要++
	    lock.unlock();
	    return true;
	}
	
	//获取当前空闲的连接数
	int connection_pool::GetFreeConn(){
	    int count;
	    lock.lock();
	    count = this->m_FreeConn;
	    lock.unlock();
	    return count;
	}
	
	//销毁数据库连接池
	
	void connection_pool::DestroyPool(){
	    lock.lock();
	
	    if(connList.size() > 0){
	        list<MYSQL*>::iterator ite;
	        for(ite = connList.begin(); ite != connList.end(); ite++){
	            MYSQL *con = *ite;
	            mysql_close(con); //关闭所有的连接
	        }
	
	        m_CurConn = 0;
	        m_FreeConn = 0;
	        connList.clear();
	    }
	    lock.unlock();
	}
	
	//实现RAII技术
	//构造函数
	connectionRAII::connectionRAII(MYSQL **SQL, connection_pool *connPool){
	    *SQL = connPool->GetConnection();
	    connRAII = *SQL;
	    poolRAII = connPool;
	}
	
	//将该链接释放掉
	connectionRAII::~connectionRAII(){
	    poolRAII->ReleaseConnection(connRAII);
	}

============================ 总结 ================================

数据库连接池的实现思路
  1. 单例模式,也就是线程不可以创建池子对象,只能调用这个对象
  2. 因为静态变量只有一份,而且只会初始化一次,所有,当某个线程(假如当前线程就是创建连接池的线程)想要获取连接池实例时:
    1. 调用 GetInstance函数,获取连接池实例
    2. 如果是调用该函数的第一个线程,连接池的构造函数connection_pool()会被调用;此时,连接池的空闲连接数和使用连接数都是0
    3. 线程获取该实例之后,调用 init函数,创建一些连接,并“存储”到 List< MYSQL* >中(具体地说,这个List其实就是存放很多连接的容器,可以说,连接池,就是,一个含有这个list容器的对象/实例);
    4. 到这里,连接池存在并且被初始化了,也就是Pool有了。
  3. 考虑这种情况,某个线程获取一个数据库连接有一种直接的方式,那就是 获取Pool实例, 直接Pool->connList.front(),就可以了;但是,为什么要引入RAII技术呢? RAII技术,他就是充分地使用了类对象自动调用构造函数和析构函数的特性,把释放资源那步放在析构函数中,会自动释放资源,所有,引入创建RAII类,通过这个RAII类对象获取一个数据库连接,然后再销毁这个对象,连接也会被释放。
    1. 创建一个RAII对象 raII,调用它的有参构造函数,这个函数会让 raii拥有一个连接和连接池(这个连接池对象需要调用GetInstance函数获得)
    2. 获取,使用,raII的连接, MYSQL* con = raII.connRAII; 通过 raII.poolRAII获取连接池的状态变量
    3. 线程结束,raII销毁,连接自动释放。
  4. 为了保证线程安全,这里用到了互斥锁,和 信号量
    1. 互斥锁的作用是,防止多个线程同时进入临界区(保护的是,m_FreeConn 和 m_CurConn 的修改唯一)
    2. 信号量的作用是,实现资源的多线程共享,这里的资源是,connList<>池子,多个线程可以同时进入池子里面获取连接。

问题

找不到 #include<mysql/mysql.h>

  1. 原因:usr的include目录中没有mysqld/mysql.h文件
  2. 解决:sudo apt-get install libmysqlclient-dev

未定义标识符 "VA_ARGS"C/C++(20)

单例模式中,如果两个线程同时调用 GetInstance函数,会发生什么?是一个线程阻塞,还是都会获得这个实例?

  1. 单例模式中的单个唯一实例可以被多个线程同时获取。
  2. 这个实例本身不是临界资源,但是在创建这个实例的过程中,需要确保线程安全,以防止多个线程同时创建多个实例。
  3. 单例模式中,虽然内存中只有一份静态实例,但是多个线程可以同时访问这个实例。
  4. 这是因为线程共享进程的地址空间,所以它们可以同时访问同一个内存地址。当多个线程同时调用获取实例的方法时,它们都会返回指向同一个内存地址的指针。

单例模式中,内存中的唯一静态实例,什么时候被销毁?如果这个实例中含有一个List<MYSQL*>容器,在析构函数中应该做些什么?这个List容器需要我手动释放掉吗?如果调用List<MYSQL*>::clear()会发生什么?

  1. 单例模式中,内存中的唯一静态实例通常在程序结束时被销毁。
  2. 如果这个实例中含有一个List< MYSQL*>容器,那么在析构函数中应该遍历这个容器,释放每个MYSQL指针指向的内存。
  3. 如果你调用List< MYSQL>::clear(),它只会清空容器中的元素,但不会释放每个MYSQL*指针指向的内存。
  4. 所以,你需要手动释放这些内存。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Geminfit

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值