【TinyWebServer源码解析】(二)半同步半反应堆线程池

本文是对github上万star项目源码解析,供大家学习交流
项目地址:
https://github.com/qinguoyi/TinyWebServer

二、半同步半反应堆线程池(threadpool)

半同步/半反应堆线程池

===============

使用一个工作队列完全解除了主线程和工作线程的耦合关系:主线程往工作队列中插入任务,工作线程通过竞争来取得任务并执行它。

​ =>同步I/O模拟proactor模式

​ =>半同步/半反应堆

​ =>线程池

===============

采用模板template <typename T>

只有一个类threadpool,类方法实现都在头文件中

包括:构造函数threadpool()、析构函数~threadpool()以及append()方法、append_p()方法、worker()方法、run()方法

===============

#include <list>
#include <cstdio>
#include <exception>
#include <pthread.h>

#include "../lock/locker.h"
#include "../CGImysql/sql_connection_pool.h"

包含以上头文件

1、类的声明
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;          //模型切换
};
2、构造函数

重点是最后一个循环,使用 pthread 库创建多个线程,并将它们分离(detach)到父线程中。循环内部会迭代 thread_number 次,每次创建一个新的线程。

具体的操作如下:

  • 通过调用 pthread_create 函数来创建新线程。该函数的第一个参数是指向线程标识符的指针,第二个参数是线程属性,第三个参数是指向函数的指针,这个函数会作为线程的入口点,并传递一个指向当前对象的指针作为参数。如果创建线程成功,则返回0。
  • 如果 pthread_create 返回非零值,则表示线程创建失败,此时代码会抛出 std::exception 异常,并删除已经创建的线程数组 m_threads。
  • 接着,通过调用 pthread_detach 函数将线程从父线程中分离。如果分离成功,则返回0。
  • 如果 pthread_detach 返回非零值,则表示线程分离失败,此时代码会抛出 std::exception 异常,并删除已经创建的线程数组 m_threads。

线程分离后,其资源将由系统自动回收。因此,无法通过调用 pthread_join() 来获取线程的返回值。

template <typename T>
/* 
	线程池的参数包括:
					actor_model 		表示选择的反应堆模型
					connPool 			表示连接池指针(connection_pool定义在sql_connection_pool中)
					thread_number 		表示线程数量
					max_requests 		表示请求队列中最大请求数量
*/
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){
// 通过形参给m_actor_model,m_thread_number,m_max_requests,m_connPool赋值、m_threads(描述线程池的数组,大小为m_thread_number)初始化为NULL
    // 线程数量or表示请求队列中最大请求数量小于等于0抛出异常
    if (thread_number <= 0 || max_requests <= 0)
        throw std::exception();
    // 创建m_threads数组,大小为m_thread_number
    m_threads = new pthread_t[m_thread_number];
    // 如果m_threads数组为null抛出异常
    if (!m_threads)
        throw std::exception();
    // 遍历m_thread数组,为线程数组分配内存,并通过循环创建线程。。worker是一个回调函数。它被传递给pthread_create作为新线程的入口点,它将在新线程上运行。这个回调函数执行线程池中实际处理任务的工作。每个线程都执行 worker 函数,worker 函数中调用run()函数,从请求队列中取出请求并处理,直到请求队列为空或者达到最大请求数
    for (int i = 0; i < thread_number; ++i)
    {
        if (pthread_create(m_threads + i, NULL, worker, this) != 0)
        {
            delete[] m_threads;
            throw std::exception();
        }
        if (pthread_detach(m_threads[i]))
        {
            delete[] m_threads;
            throw std::exception();
        }
    }
}
3、析构函数

析构函数 ~threadpool() 的作用是在线程池对象被销毁时,释放线程数组 m_threads 的内存。在该析构函数中,只有一行代码,即 delete[] m_threads;,它会释放之前在构造函数中通过 new 操作符分配的内存,因为在整个线程池生命周期中,m_threads 数组都是由线程池对象管理的。

template <typename T>
threadpool<T>::~threadpool(){
    delete[] m_threads;
}
4、append()方法

这个方法是用于向线程池工作队列中添加任务。

函数的参数包括:

  • T* request 是指向要添加的任务对象的指针
  • int state 是要为该任务设置的状态值

函数的返回值为一个布尔值,表示任务是否成功添加到工作队列中。如果工作队列已经满了,那么就不会添加到队列中,返回 false;否则将任务添加到队列中,并返回 true。

template <typename T>
bool threadpool<T>::append(T *request, int state){
    // 保护请求队列的互斥锁,上锁
    m_queuelocker.lock();
    // 检查队列是否已满,满了则解锁并返回false
    if (m_workqueue.size() >= m_max_requests){
        m_queuelocker.unlock();
        return false;
    }
    // 队列没满,给新任务的状态赋值
    request->m_state = state;
    // 添加到队列末尾
    m_workqueue.push_back(request);
    // 保护请求队列的互斥锁,解锁
    m_queuelocker.unlock();
    // post()对信号量释放,通过m_queuestat对等待的线程进行信号通知,让他们开始执行任务
    m_queuestat.post();
    return true;
}
5、append_p()方法

方法appendappend_p的区别在于:

  • append方法有一个额外的参数state,用于设置任务的状态。
  • append方法会在向工作队列中添加任务之前,将任务的状态设置为传递进来的参数state

因此,append方法比append_p方法多了一个设置任务状态的步骤。

这个状态可以被视为任务的附加信息,可能是任务的优先级、任务类型等等。通过设置任务状态,线程池可以更加灵活地控制任务的执行顺序和调度方式。

总的来说,append方法增加了对任务状态的设置,让线程池能够更好地处理不同种类的任务,并按照任务的优先级或类型进行调度。而append_p方法则是一个通用的添加任务方法,可以添加无状态的任务,仅仅是将任务添加到工作队列中,并通知等待的线程开始处理任务。

template <typename T>
bool threadpool<T>::append_p(T *request){
    // 保护请求队列的互斥锁,上锁
    m_queuelocker.lock();
    // 检查队列是否已满,满了则解锁并返回false
    if (m_workqueue.size() >= m_max_requests)
    {
        m_queuelocker.unlock();
        return false;
    }
    // 直接将指向要添加的任务对象的指针添加到队列末尾
    m_workqueue.push_back(request);
    // 保护请求队列的互斥锁,解锁
    m_queuelocker.unlock();
     // post()对信号量释放,通过m_queuestat对等待的线程进行信号通知,让他们开始执行任务
    m_queuestat.post();
    return true;
}
6、worker()方法

这个方法是一个静态函数,它是线程池的工作者线程函数,用于在线程池中执行具体任务。

template <typename T>
// 定义了一个返回类型为 void* 的 worker 函数,之前在构造函数中以回调函数形式出现
void *threadpool<T>::worker(void *arg){
    // 将传入参数 arg 强制转换为线程池指针类型,赋值给 pool 指针
    threadpool *pool = (threadpool *)arg;
    // 调用线程池对象的 run() 方法,开始执行任务
    pool->run();
    // 由于线程池对象需要在多个线程之间共享,因此将当前线程执行完毕后的线程池对象指针作为返回值返回
    return pool;
}

worker() 函数是线程池中的工作线程函数。在每次有新的任务需要执行时,会创建一个新的线程,并将该函数作为线程的入口点。在工作线程中,首先从参数中获取线程池对象的指针,然后调用线程池对象的 run() 方法执行具体的任务。最后,将线程池对象的指针作为返回值返回。

7、run()方法

该方法是线程池中的工作线程运行的方法,无限循环地从任务队列中取出任务,并执行任务中的处理逻辑。

template <typename T>
void threadpool<T>::run(){
    while (true){
        // 信号量,使当前线程等待直到任务队列中有任务可供执行
        m_queuestat.wait();
        // 获取任务队列锁
        m_queuelocker.lock();
        // 检查任务队列是否为空,如果为空则立即释放锁并继续下一次循环
        if (m_workqueue.empty()){
            m_queuelocker.unlock();
            continue;
        }
        // 如果任务队列不为空,则从任务队列中取出队头元素request,并将其从队列中删除
        T *request = m_workqueue.front();
        m_workqueue.pop_front();
        // 释放任务队列锁
        m_queuelocker.unlock();
        // 检查获取到的任务指针是否为空,如果是空指针则立即继续下一次循环
        if (!request)
            continue;
        // 如果当前采用的是Reactor模式 m_actor_model == 1
        if (1 == m_actor_model){
            // 判断任务当前状态是否为读 request->m_state == 0
            if (0 == request->m_state){
                // 如果读操作成功,调用request->read_once()方法进行读操作
                if (request->read_once()){
                    // 将request->improv属性设置为 1,表示已经得到改进
                    request->improv = 1;
                    // 创建connectionRAII对象(该类在sql_connection_pool中定义),自动获取一个数据库连接对象
                    connectionRAII mysqlcon(&request->mysql, m_connPool);
                    // 调用request->process()方法进行业务处理
                    request->process();
                }
                // 如果读操作失败
                else{
                    // 将request->improv属性设置为 1,表示已经得到改进
                    request->improv = 1;
                    // 将request->timer_flag属性设置为 1,表示需要添加一个定时器来重试该任务
                    request->timer_flag = 1;
                }
            }
            // 如果当前任务状态为写
            else{
                // 调用request->write()方法进行写操作
                if (request->write()){
                    // 如果写操作成功,则将request->improv属性设置为 1,表示已经得到改进
                    request->improv = 1;
                }
                else{
                    // 如果写操作失败,则将request->improv属性设置为 1,表示已经得到改进
                    request->improv = 1;
                    // 将request->timer_flag属性设置为 1,表示需要添加一个定时器来重试该任务
                    request->timer_flag = 1;
                }
            }
        }
        // 如果当前采用的是 Proactor 模式 m_actor_model != 1
        else{
            // 则创建connectionRAII对象,自动获取一个数据库连接对象
            connectionRAII mysqlcon(&request->mysql, m_connPool);
            // 调用request->process()方法进行业务处理
            request->process();
        }
    }
}

总体而言,该方法是线程池中的工作线程运行的主要逻辑代码,负责从任务队列中取出任务并执行任务的处理逻辑。

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值