【webserver】第2节 : 线程池介绍

目录

2.0 Linux多线程

2.0.1 Linux多线程概述

2.0.2 Linux多线程编程的API

2.0.1 线程同步

2.0.1.1 线程同步概述

2.0.1.2 线程同步的API

2.1 线程池概述

2.2 线程池的组成部分

2.2.1 请求队列

2.2.2 子线程的业务逻辑处理

2.2.3 线程池的相关实现

2.3 代码实现

2.3.1 请求队列

2.3.2 线程池


代码开源:

        https://github.com/PetterZhukov/webserver_HTTP

介绍:

        webserver_HTTP
        使用了线程池,通过epoll实现的Proactor版本的web服务器。参考了游双老师的《Linux高性能服务器编程》以及牛客网的《Linux高并发服务器开发》课程。在自己复现的基础上进行模块的整合并添加一些小更改。所有代码拥有完备的注释。

        访问的资源在 同级目录"resources"文件夹中

2.0 Linux多线程

2.0.1 Linux多线程概述

        假如程序都使用多进程编程,则会造成很多弊端,比如需要拷贝大量内存,以及进程通信较为复杂等等,因此诞生了线程这个概念。Linux中的线程被称为LWP(light weight process),即轻量的进程。

        线程共享内核以及全局内存区域,因此拷贝开销小,线程通信也容易。

2.0.2 Linux多线程编程的API

        只列出本项目会用到的API

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);

        - 功能:创建一个子线程

        - 参数:

            - thread:传出参数,线程创建成功后,子线程的线程ID被写到该变量中。

            - attr : 设置线程的属性,一般使用默认值,NULL

            - start_routine :void*(void*)类型的函数指针,这个函数是子线程需要处理的逻辑代码

            - arg : 给第三个参数使用,传参

        - 返回值:

            成功:0

            失败:返回错误号。这个错误号和之前errno不太一样。

            获取错误号的信息:  char * strerror(int errnum);

    int pthread_detach(pthread_t thread);

        - 功能:分离一个线程。被分离的线程在终止的时候,会自动释放资源返回给系统。

                  1.不能多次分离,会产生不可预料的行为。

                  2.不能去连接一个已经分离的线程,会报错。

        - 参数:需要分离的线程的ID

        - 返回值:

                    成功:0

                    失败:返回错误号

2.0.1 线程同步

2.0.1.1 线程同步概述

        线程中可能会对某一变量同时进行操作,这样是很不安全的,因此需要线程同步。

        可以使用诸如互斥锁,信号量,条件变量等来实现对应的功能。

2.0.1.2 线程同步的API

1. 互斥锁

对一个互斥锁进行加锁,则其他想对其加锁的操作都会失败,只有等到对该互斥锁进行解锁,加锁操作才可以成功,加锁操作有阻塞和非阻塞两种。

    int pthread_mutex_init(pthread_mutex_t *restrict mutex, 
            const pthread_mutexattr_t *restrict attr);
      - 初始化互斥量
      - 参数 :
         - mutex : 需要初始化的互斥量变量
         - attr : 互斥量相关的属性,NULL
      - 返回值 :
         - 成功: 0
         - 失败:错误号
    int pthread_mutex_destroy(pthread_mutex_t *mutex);
        - 释放互斥量的资源

    int pthread_mutex_lock(pthread_mutex_t *mutex);
        - 加锁(阻塞)如果有一个线程加锁了,那么其他的线程只能阻塞等待

    int pthread_mutex_unlock(pthread_mutex_t *mutex);
        - 解锁

2. 信号量

        mutex是用在不能同时操作的时候,而信号量则是用在需要计数的地方,以保证计数值大于0的时候进程才会被允许继续,在项目中构建线程池的时候,我们需要保证消息队列不为空的时候才能进行pop操作,因此需要用到信号量。

        需要注意的是信号量只保证进入的时候计数值大于0,而不保证互斥操作,因此经常和mutex一起用。

API:

    信号量的类型 sem_t
    int sem_init(sem_t *sem, int pshared, unsigned int value);
        - 初始化信号量
        - 参数:
            - sem : 信号量变量的地址
            - pshared : 0 用在线程间 ,非0 用在进程间
            - value : 信号量中的值
        - return value
            - 成功 : 0
            - 失败 : -1

    int sem_destroy(sem_t *sem);
        - 释放资源

    int sem_wait(sem_t *sem);
        - 对信号量加锁,调用一次对信号量的值-1,如果值为0,就阻塞

    int sem_post(sem_t *sem);
        - 对信号量解锁,调用一次对信号量的值+1

2.1 线程池概述

        线程池是由服务器预先创建的一组子线程。假如不使用线程池的话,有一个需要处理的队列就创建线程,没有就销毁,则频繁的创建与销毁线程会导致很大的开销。同时,线程的数量不稳定也不好控制。

        相比起来,预先创建若干数量的线程,数量是可控的,同时,线程池只有休眠和运行两种状态,只有一开始的创建和最后的销毁,运行过程中没有这方面的开销。

        面对网络服务器这样响应量大,响应时间短,对时间要求苛刻的任务,使用线程池是很合适的,若是遇到响应时间很长的任务,则线程池的作用就不大了。

2.2 线程池的组成部分

2.2.1 请求队列

        如字面意思,请求队列就是一个队列,线程会持续从中取出业务来进行业务逻辑处理。

        本项目中数据结构采用list<T*> ,即成员为指针的std::list来实现,因为只需要取头结点即可,用链表方便存取。

        请求队列需要有几个功能:

  1. 从中取元素(若为空则阻塞等待)(这用信号量来实现),即pop
  2. 往队列中加入元素,即push

2.2.2 子线程的业务逻辑处理

        因为是web服务器,以及如第一节所言,线程部分需要处理读写之后的业务逻辑。

        因此线程需要处理的部分有如下几点:

  1. 分析读的内容,解析请求报文
  2. 生成对应的回应报文

        线程需要处理的是不断从请求队列中取出的待处理业务,然后将其处理。

        对应的设计是取出类型为T的请求业务,然后调用该类的process方法(因此该类必须要有process方法),至于process具体应该实现什么,则是接下来的内容。

2.2.3 线程池的相关实现

        线程的组成成员:

                请求队列,以及若干个线程

        请求队列只需调用即可,而创建若干个线程,因为线程创建后其也只会通过修改epoll事件和处理业务逻辑产生结果来进行交互,所以和主线程没有什么需要通信的地方,因此只需创建并脱离即可。

2.3 代码实现

2.3.1 请求队列

#ifndef __QUEST_QUEUE_H_
#define __QUEST_QUEUE_H_

#include <list>
#include "locker.h"

// 模板类 请求队列
template <typename T>
class questqueue
{
private:
    // 请求队列
    std::list<T *> m_questqueue;
    // 请求队列的最大长度
    int m_max_queue;
    // 保护请求队列的互斥锁
    mutex m_queue_mutex;
    // 是否有任务需要处理,信号量
    sem m_queue_sem;

public:
    questqueue(int max_queue);
    ~questqueue();
    // 阻塞式取元素
    T *pop();
    // 阻塞式填入元素
    bool push(T *quest);
};

template <typename T>
questqueue<T>::questqueue(int max_queue) : m_max_queue(max_queue)
{
    if (max_queue <= 0)
        throw "队列的大小错误";
}

template <typename T>
questqueue<T>::~questqueue()
{
    for (auto it = m_questqueue.begin(); it != m_questqueue.end(); it++)
        delete *it;
}

// 阻塞式填入元素
template <typename T>
bool questqueue<T>::push(T *quest)
{
    // 操作请求队列加锁
    m_queue_mutex.lock(); // lock
    if (m_questqueue.size() >= m_max_queue)
    {
        return false;
    }

    // 添加quest,并且更新
    m_questqueue.push_back(quest);
    m_queue_sem.post();

    m_queue_mutex.unlock(); // unlock
    return true;
}

// 阻塞式取元素
template <typename T>
T *questqueue<T>::pop()
{
    // 上锁
    m_queue_sem.wait();
    m_queue_mutex.lock();
    if (m_questqueue.empty())
    {
        m_queue_mutex.unlock();
        return NULL;
    }
    // 取出
    T *quest = m_questqueue.front();
    m_questqueue.pop_front();
    // 解锁
    m_queue_mutex.unlock();

    return quest;
}

#endif

2.3.2 线程池

#ifndef _PTHREADPOOL_H_
#define _PTHREADPOOL_H_

#include <list>
#include "locker.h"
#include "questqueue.h"

// 模板类 线程池
template <typename T>
class threadpool
{
public:
    threadpool(int poolsize = 8, int maxquest = 1000);
    ~threadpool();
    // 增加请求
    bool append(T *quest);

private:
    // 子线程调用的的执行函数
    static void *worker(void *arg);
    // 因为worker是静态的,因此增加一个真正的执行函数
    void run();

private:
    // 请求队列
    questqueue<T> m_questqueue;
    // 线程池大小
    int m_thread_poolsize;
    // 大小为m_thread_poolsize的 线程池
    pthread_t *m_threads;

    // 是否结束线程
    bool m_stoppool;
};

template <typename T>
threadpool<T>::threadpool(int poolsize, int maxquest) : 
    m_thread_poolsize(poolsize), m_stoppool(false),m_questqueue(maxquest)
{
    // check size
    if (poolsize <= 0)
        throw "线程池的大小错误";

    m_threads = new pthread_t[m_thread_poolsize];

    // 初始化线程池的线程
    for (int i = 0; i < m_thread_poolsize; i++)
    {
        #ifdef  show_create_pool
            printf( "create the %dth thread\n", i+1);
        #endif
        if (pthread_create(m_threads + i, NULL, worker, this) != 0)
        {
            delete[] m_threads;
            throw "创建子线程时错误";
        }
    }
    for (int i = 0; i < m_thread_poolsize; i++)
    {
        if (pthread_detach(m_threads[i]) != 0)
        {
            delete[] m_threads;
            throw "子线程分离时错误";
        }
    }
    printf("thread pool ready \n");
}

template <typename T>
threadpool<T>::~threadpool()
{
    m_stoppool = true;
    delete[] m_threads;
}

template <typename T>
bool threadpool<T>::append(T *quest)
{
    return m_questqueue.push(quest);
}

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 (!m_stoppool)
    {
        // 从请求队列中阻塞取出待处理元素
        T* quest=m_questqueue.pop();

        // 检查是否为空
        if (quest!=NULL)
            // 调用quest
            quest->process();
    }
}

#endif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值