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

该博客围绕Linux多线程和线程池展开,介绍了Linux多线程的概念、编程API及线程同步方法。阐述了线程池的优势、组成部分,包括请求队列、子线程业务逻辑处理等。还给出了请求队列和线程池的代码实现,最终实现一个使用线程池和epoll的Proactor版本web服务器。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

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、付费专栏及课程。

余额充值