文章目录
1 - main函数
main函数中,我们做的就是:
- 处理SIGPIPE信号,防止因写异常关闭进程
- 初始化一个内核事件表
- 创建server socket,绑定端口,开始监听;将server socket设置为非阻塞
- 将server socket与对应的事件(事件已经设置好request内容)注册到内核事件表中
- 进入工作循环(就是死循环,有异常会直接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
线程池的声明
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;
}