WebServer代码解读(1)【main函数流程】【忽略SIGPIPE信号:handle_for_sigpipe】【创建EPOLL内核事件表】【线程池组成部分与工作逻辑】

1 - main函数

main函数中,我们做的就是:

  1. 处理SIGPIPE信号,防止因写异常关闭进程
  2. 初始化一个内核事件表
  3. 创建server socket,绑定端口,开始监听;将server socket设置为非阻塞
  4. 将server socket与对应的事件(事件已经设置好request内容)注册到内核事件表中
  5. 进入工作循环(就是死循环,有异常会直接break),开始等待事件发生,事件发生后处理事件

下面来分别介绍main函数中的几个步骤的操作与关键函数(不局限于本文,本系列的其他代码解读系列均为后续)

1-1 handle_for_sigpipe

handle_for_sigpipe():忽略SIGPIPE信号,防止因为错误的写操作(向读端关闭的socket中写数据)而导致进程退出

在tcp四次挥手过程中,发送方 向已经调用close()方法的socket一端写数据,会产生sigpipe错误

当服务器close一个连接时,若client端接着发数据(第一次调用write),根据TCP协议的规定,会收到一个RST响应,client再往这个服务器发送数据时(第二次调用write),系统会发出一个SIGPIPE信号给进程,告诉进程这个连接已经断开了,不要再写了,导致进程退出。

close():关闭读写两个方向,会导致sigpipe信号
shutdown():可以选择关闭读/写方向,不会导致sigpipe信号

参考:SIGPIPE信号详解

///忽略SIGPIPE信号,防止进程退出
void handle_for_sigpipe()
{
    
    
    struct sigaction sa;
    memset(&sa, '\0', sizeof(sa));
    sa.sa_handler = SIG_IGN;//指定信号处理函数,SIG_IGN忽略目标信号
    sa.sa_flags = 0;		//设置程序收到信号时的行为
    if(sigaction(SIGPIPE, &sa, NULL))
        return;
}

1-2 epoll_init

epoll_init():创建epoll句柄(内核事件表)并初始化epoll,返回值为epfd

///创建epoll句柄(内核事件表)并初始化epoll
int epoll_init()
{
    
    
    //内核事件表
    int epoll_fd = epoll_create(LISTENQ + 1);
    if(epoll_fd == -1)
        return -1;
    //events = (struct epoll_event*)malloc(sizeof(struct epoll_event) * MAXEVENTS);
    
    //初始化事件数组,MAXEVENTS为最大关注socket fd数量
    events = new epoll_event[MAXEVENTS];
    return epoll_fd;
}

1-3 threadpool

线程池的组成部分(概念)

线程池在概念上由四部分组成:

  • 1、线程池管理器:创建一定数量的线程,启动线程,调配任务,管理着线程池。
  • 2、工作线程:在线程池中等到执行task
  • 3、任务队列:存放没有处理的task,是一种缓冲机制
  • 4、任务:task

参考:基于C++11实现线程池的工作原理

线程池的声明

threadpool_create():创建并初始化线程池

///创建线程池,用 thread_count 指定派生线程数,queue_size 指定任务队列长度,flags 为保留参数,未使用。
threadpool_t *threadpool_create(int thread_count, int queue_size, int flags);

线程池的组成部分(代码与逻辑)

那么首先要明确一个线程池由什么组成

threadpool_t:线程池结构体

struct threadpool_t
{
    
    
    pthread_mutex_t lock;       //用于内部工作的互斥锁
    pthread_cond_t notify;      //线程间通知的条件变量
    pthread_t *threads;         //线程数组,这里用指针来表示,数组名 = 首元素指针
    threadpool_task_t *queue;   //任务队列数组的起始指针
    int thread_count;           //线程数量
    int queue_size;             //任务队列长度
    int head;                   //任务队列中首个任务位置(注:任务队列中所有任务都是未开始运行的)
    int tail;                   //任务队列中最后一个任务的下一个位置(注:队列以数组存储,head 和 tail 指示队列位置)
    int count;                  //任务队列里的任务数量,即等待运行的任务数
    int shutdown;               //表示线程池是否关闭
    int started;                //正在运行的线程数
};

threadpool_task_t:线程池队列内任务

typedef struct {
    
    
    void (*function)(void *);   //要运行的函数
    void *argument;             //要运行函数的参数
} threadpool_task_t;

创建线程池有哪几步

创建线程池的工作包含:

1、申请内存创建线程池对象

2、初始化线程池的成员变量,如初始线程数量=0,初始任务队列长度=0,初始任务队列头尾与任务个数,初始线程池状态

3、申请内存创建线程数组、任务队列

4、初始化互斥锁和条件变量。因为同一时刻,只能有一个线程对任务队列进行操作(生产者-消费者模式),即要么是一个生产者线程向task queue中添加task,要么是一个消费者线程从task queue中取出task。所以,需要使用条件变量与互斥锁保证并发操作。

5、创建线程开始运行,运行的是每个线程指定的函数。该函数首先对task queue加锁,触发条件后取出task queue中第一个任务,更新task queue,释放互斥锁(因为对task queue的操作已经完成),然后去执行取出的task对应的任务专属函数(相当于执行任务)。

创建线程池代码

下面代码的声明已经在前面提到过,下面代码是创建线程池的具体实现:

///创建线程池
threadpool_t *threadpool_create(int thread_count, int queue_size, int flags)
{
    
    
    threadpool_t *pool;
    int i;
    //(void) flags;
    do
    {
    
    
        if(thread_count <= 0 || thread_count > MAX_THREADS || queue_size <= 0 || queue_size > MAX_QUEUE) {
    
    
            return NULL;
        }
        //申请内存创建线程池对象
        if((pool = (threadpool_t *)malloc(sizeof(threadpool_t))) == NULL) 
        {
    
    
            break;
        }
    
        /* Initialize */
        pool->thread_count = 0;
        pool->queue_size = queue_size;
        pool->head = pool->tail = pool->count = 0;
        pool->shutdown = pool->started = 0;
    
        /* Allocate thread and task queue */
        /* 申请线程数组和任务队列所需的内存 */
        pool->threads = (pthread_t *)malloc(sizeof(pthread_t) * thread_count);
        pool->queue = (threadpool_task_t *)malloc(sizeof(threadpool_task_t) * queue_size);
    
        /* Initialize mutex and conditional variable first */
        /* 初始化互斥锁和条件变量 */
        if((pthread_mutex_init(&(pool->lock), NULL) != 0) ||
           (pthread_cond_init(&(pool->notify), NULL) != 0) ||
           (pool->threads == NULL) ||
           (pool->queue == NULL)) 
        {
    
    
            break;
        }
    
        /* Start worker threads */
        /* 创建指定数量的线程开始运行 */
        for(i = 0; i < thread_count; i++) {
    
    
            if(pthread_create(&(pool->threads[i]), NULL, threadpool_thread, (void*)pool) != 0) 
            {
    
    
                threadpool_destroy(pool, 0);
                return NULL;
            }
            pool->thread_count++;
            pool->started++;
        }
    
        return pool;
    } while(false);
    
    if (pool != NULL) 
    {
    
    
        threadpool_free(pool);
    }
    return NULL;
}

每个线程执行的函数:

static void *threadpool_thread(void *threadpool)
{
    
    
    threadpool_t *pool = (threadpool_t *)threadpool;
    threadpool_task_t task;

    for(;;)
    {
    
    
        /* Lock must be taken to wait on conditional variable */
        /* 取得互斥锁资源 */
        pthread_mutex_lock(&(pool->lock));

        /* Wait on condition variable, check for spurious wakeups.
           When returning from pthread_cond_wait(), we own the lock. */
        /* 用 while 是为了在唤醒时重新检查条件 */
        while((pool->count == 0) && (!pool->shutdown)) 
        {
    
       /* 任务队列为空,且线程池没有关闭时阻塞在这里 */
            //函数内发生:1.释放互斥锁 2.等待条件 3.条件被触发、互斥锁加锁
            pthread_cond_wait(&(pool->notify), &(pool->lock));
        }

        /* 关闭的处理 */
        if((pool->shutdown == immediate_shutdown) ||
           ((pool->shutdown == graceful_shutdown) &&
            (pool->count == 0)))
        {
    
    
            break;
        }

        /* Grab our task */
        /* 取得任务队列的第一个任务 */
        task.function = pool->queue[pool->head].function;
        task.argument = pool->queue[pool->head].argument;
        /* 更新 head 和 count */
        pool->head = (pool->head + 1) % pool->queue_size;
        pool->count -= 1;

        /* Unlock */
        /* 释放互斥锁 */
        pthread_mutex_unlock(&(pool->lock));

        /* Get to work */
        /* 开始运行任务 */
        (*(task.function))(task.argument);
        /* 这里一个任务运行结束 */
    }

    /* 线程将结束,更新运行线程数 */
    --pool->started;

    pthread_mutex_unlock(&(pool->lock));
    pthread_exit(NULL);
    return(NULL);
}

销毁线程池

销毁线程池的步骤如下:

1、对互斥锁加锁。确保没有工作线程对线程池加锁,也就是确保没有线程处在工作状态了

2、唤醒所有阻塞在条件变量上的线程,释放互斥锁。在销毁线程池之前,将阻塞的线程全部唤醒去尽力执行任务。

3、等待所有线程结束,释放线程池占用的内存

4、释放pool的所有动态申请的内存空间,如 线程数组(free) 任务队列(free) 互斥锁(destroy) 条件变量(destroy) 线程池本身(destroy)

int threadpool_destroy(threadpool_t *pool, int flags)
{
    
    
    printf("Thread pool destroy !\n");
    int i, err = 0;

    if(pool == NULL)
    {
    
    
        return THREADPOOL_INVALID;
    }

    /* 取得互斥锁资源 */
    if(pthread_mutex_lock(&(pool->lock)) != 0) 
    {
    
    
        return THREADPOOL_LOCK_FAILURE;
    }

    do 
    {
    
    
        /* Already shutting down */
        /* 判断是否已在其他地方关闭 */
        if(pool->shutdown) {
    
    
            err = THREADPOOL_SHUTDOWN;
            break;
        }

        /* 获取指定的关闭方式 */
        pool->shutdown = (flags & THREADPOOL_GRACEFUL) ?
            graceful_shutdown : immediate_shutdown;

        /* Wake up all worker threads */
        /* 唤醒所有因条件变量阻塞的线程,并释放互斥锁 */
        if((pthread_cond_broadcast(&(pool->notify)) != 0) ||
           (pthread_mutex_unlock(&(pool->lock)) != 0)) {
    
    
            err = THREADPOOL_LOCK_FAILURE;
            break;
        }

        /* Join all worker thread */
        /* 等待所有线程结束 */
        for(i = 0; i < pool->thread_count; ++i)
        {
    
    
            if(pthread_join(pool->threads[i], NULL) != 0)
            {
    
    
                err = THREADPOOL_THREAD_FAILURE;
            }
        }
    } while(false);

    /* Only if everything went well do we deallocate the pool */
    if(!err) 
    {
    
    
        /* 释放内存资源 */
        threadpool_free(pool);
    }
    return err;
}

int threadpool_free(threadpool_t *pool)
{
    
    
    if(pool == NULL || pool->started > 0)
    {
    
    
        return -1;
    }

    /* Did we manage to allocate ? */
    if(pool->threads) 
    {
    
    
        /* 释放线程 任务队列 互斥锁 条件变量 线程池所占内存资源 */
        free(pool->threads);
        free(pool->queue);
 
        /* Because we allocate pool->threads after initializing the
           mutex and condition variable, we're sure they're
           initialized. Let's lock the mutex just in case. */
        pthread_mutex_lock(&(pool->lock));
        pthread_mutex_destroy(&(pool->lock));
        pthread_cond_destroy(&(pool->notify));
    }
    free(pool);    
    return 0;
}

Guess you like

Origin blog.csdn.net/weixin_44484715/article/details/116948630