【后台开发】TinyWebser学习笔记(2)线程池、数据库连接池

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 【后台开发】TinyWebser学习笔记(2)线程池、数据库连接池

前面讲到了服务器的基本架构,接下来讲讲支持服务器得以高并发的线程池和数据库连接池的构建。

一、线程池的实现

线程池就是首先创建一些线程,它们的集合称为线程池。线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。

为什么需要线程池?

从上面对线程池的描述可知,线程池是预先创建的,负责执行服务器分配的任务,任务完成之后,线程空闲,以待执行另外的任务。

这就体现了线程池的两个好处:

第一、用完线程不销毁,也就避免了因为线程创建和销毁过程造成的时间损耗,提高服务器的并发性。

第二、预先创建一个线程池,也就避免了因为大量客户端建立连接,而去新建线程,以至于造成服务器资源消耗殆尽从而崩溃的风险!提高了服务器的鲁棒性。


下面来看看,如何实现一个线程池:

template <typename T>
class threadpool
{
public:
    /*thread_number是线程池中线程的数量,max_requests是请求队列中最多允许的、等待处理的请求的数量*/
    threadpool(int actor_model, connection_pool *connPool, int thread_number = 8, int max_request = 10000);
    ~threadpool();
    bool append(T *request, int state);
    bool append_p(T *request);

private:
    /*工作线程运行的函数,它不断从工作队列中取出任务并执行之*/
    static void *worker(void *arg);//为什么要用静态成员函数呢?
    void run();

private:
    int m_thread_number;        //线程池中的线程数
    int m_max_requests;         //请求队列中允许的最大请求数
    pthread_t *m_threads;       //描述线程池的数组,其大小m_thread_number
    std::list<T *> m_workqueue; //请求队列
    locker m_queuelocker;       //保护请求队列的互斥锁
    sem m_queuestat;            //是否有任务需要处理
    connection_pool *m_connPool;  //数据库
    int m_actor_model;          //模型切换(这个切换是指?)
};
template <typename T>
threadpool<T>::threadpool( int actor_model, connection_pool *connPool, int thread_number, int max_requests) : m_actor_model(actor_model),m_thread_number(thread_number), m_max_requests(max_requests), m_threads(NULL),m_connPool(connPool)
{
    if (thread_number <= 0 || max_requests <= 0)
        throw std::exception();
    m_threads = new pthread_t[m_thread_number];     //pthread_t是长整型
    if (!m_threads)
        throw std::exception();
    for (int i = 0; i < thread_number; ++i)
    {
        if (pthread_create(m_threads + i, NULL, worker, this) != 0)
        {   //线程创建成功则返回0,如线程池创建失败则应关闭线程池
            delete[] m_threads;
            throw std::exception();
        }
        if (pthread_detach(m_threads[i])) //主要是将线程属性更改为unjoinable,便于资源的释放,详见PPPPS
        {
            delete[] m_threads;
            throw std::exception();
        }
    }
}
template <typename T>
threadpool<T>::~threadpool()
{
    delete[] m_threads;
}
template <typename T>
bool threadpool<T>::append(T *request, int state)
{
    m_queuelocker.lock();
    if (m_workqueue.size() >= m_max_requests)
    {
        m_queuelocker.unlock();
        return false;
    }
    request->m_state = state;
    m_workqueue.push_back(request);
    m_queuelocker.unlock();
    m_queuestat.post();
    return true;
}
template <typename T>
bool threadpool<T>::append_p(T *request)
{
    m_queuelocker.lock();
    if (m_workqueue.size() >= m_max_requests)
    {
        m_queuelocker.unlock();
        return false;
    }
    m_workqueue.push_back(request);
    m_queuelocker.unlock();
    m_queuestat.post();
    return true;
}
template <typename T>
void *threadpool<T>::worker(void *arg)
{
    threadpool *pool = (threadpool *)arg;
    pool->run();
    return pool;
}
template <typename T>
void threadpool<T>::run()
{
    while (true)
    {
        m_queuestat.wait();
        m_queuelocker.lock();
        if (m_workqueue.empty())
        {
            m_queuelocker.unlock();
            continue;
        }
        T *request = m_workqueue.front();
        m_workqueue.pop_front();
        m_queuelocker.unlock();
        if (!request)
            continue;
        if (1 == m_actor_model)
        {
            if (0 == request->m_state)
            {
                if (request->read_once())
                {
                    request->improv = 1;
                    connectionRAII mysqlcon(&request->mysql, m_connPool);
                    request->process();
                }
                else
                {
                    request->improv = 1;
                    request->timer_flag = 1;
                }
            }
            else
            {
                if (request->write())
                {
                    request->improv = 1;
                }
                else
                {
                    request->improv = 1;
                    request->timer_flag = 1;
                }
            }
        }
        else
        {
            connectionRAII mysqlcon(&request->mysql, m_connPool);
            request->process();
        }
    }
}

PS:模板类的初始化以及函数定义的格式即是带模板格式进行定义,如上例所示;

PPS:线程池构造的基本原理即是预先申请一堆线程资源,而后根据新来的任务请求进行线程分配;

PPPS:从上述代码可以看出异常处理机制以及资源获取失败后释放资源链接的重要性。

PPPPS: linux线程pthread有两种状态joinable状态和unjoinable状态:如果线程是joinable状态,当线程函数自己返回退出时或pthread_exit时都不会释放线程所占用堆栈和线程描述符(总计8K多)。

只有当你调用了pthread_join(pthread_join()即是子线程合入主线程,主线程阻塞等待子线程结束,然后回收子线程资源。

之后这些资源才会被释放。若是unjoinable状态的线程,这些资源在线程函数退出时或pthread_exit时自动会被释放。

而unjoinable属性可以在pthread_create时指定,或在线程创建后在线程中pthread_detach(pthread_detach()即主线程与子线程分离,子线程结束后,资源自动回收), 如:pthread_detach(pthread_self()),将状态改为unjoinable状态,确保资源的释放。其实简单的说就是在线程函数头加上 pthread_detach(pthread_self())的话,线程状态改变,在函数尾部直接 pthread_exit线程就会自动退出。省去了给线程擦屁股的麻烦。

二、数据库连接池的实现

class connection_pool
{
public:
    MYSQL *GetConnection();              //获取数据库连接
    bool ReleaseConnection(MYSQL *conn); //释放连接
    int GetFreeConn();                   //获取连接
    void DestroyPool();                  //销毁所有连接

    //单例模式 这个是利用局部静态变量懒汉模式实现单例(多线程不友好)
    //对于多线程而言,多个线程可能同时访问到该静态变量,并发现其没有被初始化(C++实现机制是)
    //(该看静态变量内部一标志位是为1,为1则说明已被初始化)
    //多个线程同时判定其没有初始化而后均初始化就会造成错误(即它不是一个原子操作)
    static connection_pool *GetInstance();

    void init(string url, string User, string PassWord, string DataBaseName, int Port, int MaxConn, int close_log); 

private:
    connection_pool();
    ~connection_pool();

    int m_MaxConn;  //最大连接数
    int m_CurConn;  //当前已使用的连接数
    int m_FreeConn; //当前空闲的连接数
    locker lock;
    list<MYSQL *> connList; //连接池
    sem reserve;

public:
    string m_url;            //主机地址
    string m_Port;       //数据库端口号
    string m_User;       //登陆数据库用户名
    string m_PassWord;   //登陆数据库密码
    string m_DatabaseName; //使用数据库名
    int m_close_log;    //日志开关
};

class connectionRAII{

public:
    connectionRAII(MYSQL **con, connection_pool *connPool);
    ~connectionRAII();
    
private:
    MYSQL *conRAII;
    connection_pool *poolRAII;
};

PS:RAII(Resource Acquisition Is Initialization),也称为“资源获取就是初始化”,是c++等编程语言常用的管理资源、避免内存泄露的方法。它保证在任何情况下,使用对象时先构造对象,最后析构对象。

其基本的实现思想:当我们在一个函数内部使用局部变量,当退出了这个局部变量的作用域时,这个变量也就别销毁了;当这个变量是类对象时,这个时候,就会自动调用这个类的析构函数,而这一切都是自动发生的,不要程序员显示的去调用完。

PS2:数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。这项技术能明显提高对数据库操作的性能。这跟线程池的理念基本一致,即实现申请一堆数据库连接,而后谁用谁拿走,不用就空闲等待下个线程使用。

PS3:数据库连接的获取和释放通过信号量来实现:初始化信号量为连接池中的数量,销毁则信号量+1,获取则信号量-1,如果信号量为0说明连接池为空,那就一直等待;

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
1月前
|
编解码 数据安全/隐私保护 计算机视觉
Opencv学习笔记(十):同步和异步(多线程)操作打开海康摄像头
如何使用OpenCV进行同步和异步操作来打开海康摄像头,并提供了相关的代码示例。
85 1
Opencv学习笔记(十):同步和异步(多线程)操作打开海康摄像头
|
1月前
|
存储 SQL 关系型数据库
Mysql学习笔记(二):数据库命令行代码总结
这篇文章是关于MySQL数据库命令行操作的总结,包括登录、退出、查看时间与版本、数据库和数据表的基本操作(如创建、删除、查看)、数据的增删改查等。它还涉及了如何通过SQL语句进行条件查询、模糊查询、范围查询和限制查询,以及如何进行表结构的修改。这些内容对于初学者来说非常实用,是学习MySQL数据库管理的基础。
131 6
|
1月前
|
SQL Ubuntu 关系型数据库
Mysql学习笔记(一):数据库详细介绍以及Navicat简单使用
本文为MySQL学习笔记,介绍了数据库的基本概念,包括行、列、主键等,并解释了C/S和B/S架构以及SQL语言的分类。接着,指导如何在Windows和Ubuntu系统上安装MySQL,并提供了启动、停止和重启服务的命令。文章还涵盖了Navicat的使用,包括安装、登录和新建表格等步骤。最后,介绍了MySQL中的数据类型和字段约束,如主键、外键、非空和唯一等。
71 3
Mysql学习笔记(一):数据库详细介绍以及Navicat简单使用
|
4月前
|
SQL druid Java
线程池相关故障问题之Druid数据库连接池中,为何需要设置TransactionTimeout
线程池相关故障问题之Druid数据库连接池中,为何需要设置TransactionTimeout
130 0
|
1月前
|
Java 关系型数据库 MySQL
如何用java的虚拟线程连接数据库
本文介绍了如何使用Java虚拟线程连接数据库,包括设置JDK版本、创建虚拟线程的方法和使用虚拟线程连接MySQL数据库的示例代码。
45 6
如何用java的虚拟线程连接数据库
|
2月前
|
SQL 关系型数据库 MySQL
php学习笔记-连接操作mysq数据库(基础)-day08
本文介绍了PHP中连接操作MySQL数据库的常用函数,包括连接服务器、设置字符集、关闭连接、选择数据库、结果集释放、获取影响行数以及遍历结果集等操作。通过书籍查询的实例演示了如何使用这些函数进行数据库操作,并提供了一个PHP操纵MySQL数据库的模板。
php学习笔记-连接操作mysq数据库(基础)-day08
|
1月前
FFmpeg学习笔记(二):多线程rtsp推流和ffplay拉流操作,并储存为多路avi格式的视频
这篇博客主要介绍了如何使用FFmpeg进行多线程RTSP推流和ffplay拉流操作,以及如何将视频流保存为多路AVI格式的视频文件。
174 0
|
3月前
|
调度
多线程学习笔记
这篇文章是多线程学习笔记,涵盖了线程与进程的概念、多线程实现方式、线程状态、线程同步与不安全示例、死锁问题以及生产者与消费者问题等多线程编程的关键知识点。
多线程学习笔记
|
3月前
|
SQL druid Java
Java数据库部分(MySQL+JDBC)(二、JDBC超详细学习笔记)(下)
Java数据库部分(MySQL+JDBC)(二、JDBC超详细学习笔记)
56 3
Java数据库部分(MySQL+JDBC)(二、JDBC超详细学习笔记)(下)
|
3月前
|
SQL Java 关系型数据库
Java数据库部分(MySQL+JDBC)(二、JDBC超详细学习笔记)(上)
Java数据库部分(MySQL+JDBC)(二、JDBC超详细学习笔记)
128 3
Java数据库部分(MySQL+JDBC)(二、JDBC超详细学习笔记)(上)