线程池的作用是为了避免重复创建销毁线程,这篇文章在上一篇的基础上加上了线程池。
反应堆代码在上一篇文章中:epoll反应堆-优快云博客https://blog.youkuaiyun.com/qq_53137764/article/details/136168312?spm=1001.2014.3001.5501
加上线程池需要注意的点是,需要将默认LT模式改为ET模式,如果是LT模式,当线程池没有将某一任务完成(没有将数据读出来),epoll_wait会一直触发读事件,导致主线程一直往任务队列中添加相同任务。
代码如下(为了省事这里我没有将read函数循环读数据):
#ifndef _THRPOOL_REACTOR_H
#define _THRPOOL_REACTOR_H
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<sys/epoll.h>
#include<unistd.h>
#include<pthread.h>
#include"wrap.h"
#define _EVENT_SIZE_ 1024
// 事件驱动结构体
typedef struct xx_event {
int fd;
int events;
void (*call_back)(int fd, int events, void* arg);
void* arg; // 回调参数
char buf[1024];
int buflen;
int epfd;
int flag;
}xevent;
// 任务结构体
typedef struct _PoolTask
{
xevent *ev;
}Task;
// 线程池
typedef struct _ThreadPool
{
int max_job_num; // 最大任务个数
int job_num; // 实际任务个数
Task* tasks; // 任务队列数组
int job_push; // 入队位置
int job_pop; // 出队位置
int thr_num; // 线程池内的线程个数
pthread_t* threads; // 线程池内线程数组
int shutdown; // 是否关闭线程池
pthread_mutex_t pool_lock; // 线程池的锁
pthread_cond_t empty_task; // 任务队列为空的条件
pthread_cond_t not_empty_task; // 任务队列不为空的条件
}ThreadPool;
void eventAdd(int fd, int events, void (*call_back)(int, int, void*), xevent* ev);// 添加事件
void eventSet(int fd, int events, void (*call_back)(int, int, void*), xevent* ev);// 修改事件
void eventDel(xevent* ev, int fd, int events);// 删除事件
void initAccept(int fd, int events, void* xev);// 新连接处理(监听套接字回调)
void sendData(int fd, int events, void* xev);// 写数据(客户端任务、线程池处理)
void readData(int fd, int events, void* xev);// 读数据(客户端任务、线程池处理)
void createThreadpool(int thrnum, int maxtasknum);// 创建线程池
void addTask(Task task);// 添加任务到线程池
void destroyThreadpool(ThreadPool* pool);// 摧毁线程池
#endif
#include"threadPool_reactor.h"
int gepfd = 0; // 全局epoll树的根
xevent myevents[_EVENT_SIZE_ + 1];
ThreadPool* thrPool = NULL; // 线程池
// 添加事件
void eventAdd(int fd, int events, void (*call_back)(int, int, void*), xevent* ev)
{
// 对myevents数组添加
ev->fd = fd;
ev->events = events;
ev->call_back = call_back;
// 对epoll对象添加
struct epoll_event epv;
epv.events = events;
epv.data.ptr = ev; // 核心
epoll_ctl(gepfd, EPOLL_CTL_ADD, fd, &epv); // 上树
}
// 修改事件
void eventSet(int fd, int events, void (*call_back)(int, int, void*), xevent* xev)
{
xev->fd = fd;
xev->events = events;
xev->call_back = call_back;
xev->arg = xev;
struct epoll_event epv;
epv.events = events;
epv.data.ptr = xev;
epoll_ctl(gepfd, EPOLL_CTL_MOD, fd, &epv); // 修改
}
// 删除事件
void eventDel(xevent* xev, int fd, int events)
{
// 先在myevents数组中清除
xev->fd = 0;
xev->events = 0;
xev->call_back = NULL;
memset(xev->buf, 0x00, sizeof(xev->buf));
xev->buflen = 0;
xev->arg = NULL;
// 下树
struct epoll_event epv;
epv.data.ptr = NULL;
epv.events = events;
epoll_ctl(gepfd, EPOLL_CTL_DEL, fd, &epv);
}
// 写数据
void sendData(int fd, int events, void* arg)
{
xevent *xev = (xevent*)arg;
Write(xev->fd, xev->buf, xev->buflen);
eventSet(fd, EPOLLIN | EPOLLET, readData, xev);
}
// 读数据
void readData(int fd, int events, void* arg)
{
xevent *xev = (xevent*)arg;
xev->buflen = read(xev->fd, xev->buf, sizeof(xev->buf));
if (xev->buflen > 0) // 读到数据
{
eventSet(fd, EPOLLOUT | EPOLLET, sendData, xev); // 修改成写事件
}
else if (xev->buflen == 0) // 对方关闭连接
{
close(xev->fd);
eventDel(xev, fd, EPOLLIN | EPOLLET);
}
}
// 新连接处理
void initAccept(int fd, int events, void* arg)
{
struct sockaddr_in addr;
socklen_t len = sizeof(addr);
int cfd = Accept(fd, (struct sockaddr*)&addr, &len);
// 查找myevents数组中可用的位置
int i;
for (i = 0; i < _EVENT_SIZE_; i++)
{
if (myevents[i].fd <= 0)
{
break;
}
}
if (i == _EVENT_SIZE_)
{
return;
}
// 将cfd上树,并设置设置读事件
eventAdd(cfd, EPOLLIN | EPOLLET, readData, &myevents[i]);
}
// 取任务做任务
void thrRun(void* arg)
{
int taskpos = 0; // 任务位置
Task* task;
while (1)
{
// 获取任务,先要尝试加锁
pthread_mutex_lock(&thrPool->pool_lock);
// 无任务并且线程池不是要摧毁
while (thrPool->job_num <= 0 && !thrPool->shutdown)
{
// 如果没有任务,使用条件变量让线程阻塞等待新任务加入
pthread_cond_wait(&thrPool->not_empty_task, &thrPool->pool_lock);
}
if (thrPool->job_num)
{
// 有任务需要处理
taskpos = (thrPool->job_pop++) % thrPool->max_job_num;
printf("task position === %d... cfd === %d tid = %lu\n", taskpos, thrPool->tasks[taskpos].ev->fd, pthread_self());
// 为什么要拷贝?避免任务被修改,生产者会添加任务
//memcpy(task, &thrPool->tasks[taskpos], sizeof(Task));
thrPool->job_num--;
task = &thrPool->tasks[taskpos];
pthread_cond_signal(&thrPool->empty_task); // 通知生产者
}
if (thrPool->shutdown)
{
// 代表要摧毁线程池,此时线程退出即可
pthread_mutex_unlock(&thrPool->pool_lock);
free(task);
pthread_exit(NULL);
}
// 先释放锁,再去执行回调
pthread_mutex_unlock(&thrPool->pool_lock);
xevent* xev = task->ev;
xev->call_back(xev->fd, xev->events, xev); // 执行回调函数
}
}
// 创建线程池
void createThreadpool(int thrnum, int maxtasknum)
{
// 初始化线程池
thrPool = (ThreadPool*)malloc(sizeof(ThreadPool));
thrPool->thr_num = thrnum;
thrPool->max_job_num = maxtasknum;
thrPool->shutdown = 0; // 是否摧毁线程池,1代表摧毁
thrPool->job_push = 0; // 任务队列添加的位置
thrPool->job_pop = 0; // 任务队列出队的位置
thrPool->job_num = 0; // 初始化的任务个数为0
thrPool->threads = (pthread_t*)malloc(sizeof(pthread_t) * thrnum); // 线程数组
thrPool->tasks = (Task*)malloc((sizeof(Task) * maxtasknum)); // 任务队列
for (int i = 0; i < maxtasknum; ++i)
{
thrPool->tasks[i].ev = (xevent*)malloc(sizeof(xevent)); // 为每个任务的事件结构体分配内存
}
// 初始化锁和条件变量
pthread_mutex_init(&(thrPool->pool_lock), NULL);
pthread_cond_init(&(thrPool->empty_task), NULL);
pthread_cond_init(&(thrPool->not_empty_task), NULL);
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); // 线程分离
int i = 0;
for (i = 0; i < thrnum; i++)
pthread_create(&thrPool->threads[i], &attr, (void*)thrRun, NULL); // 创建多个线程,回调函数thrRun(线程取任务做任务)
}
// 添加任务到线程池
void addTask(Task task)
{
pthread_mutex_lock(&(thrPool->pool_lock)); // 上锁
// 实际任务总数大于最大任务个数则阻塞(等待任务被处理)
while (thrPool->max_job_num <= thrPool->job_num)
pthread_cond_wait(&(thrPool->empty_task), &(thrPool->pool_lock)); // 等待任务队列有空位置
int taskpos = (thrPool->job_push++) % thrPool->max_job_num; // 任务的添加位置
printf("add task on position %d... from fd === %d\n", taskpos, task.ev->fd); // 添加来自fd==?的任务
thrPool->tasks[taskpos] = task;
thrPool->job_num++;
pthread_mutex_unlock(&thrPool->pool_lock); // 解锁
pthread_cond_signal(&thrPool->not_empty_task); // 通知线程有任务来了
}
// 摧毁线程池
void destroy_threadpool(ThreadPool* pool)
{
pool->shutdown = 1;
pthread_cond_broadcast(&pool->not_empty_task); // 诱杀
int i = 0;
for (i = 0; i < pool->thr_num; i++)
{
pthread_join(pool->threads[i], NULL);
}
pthread_cond_destroy(&pool->not_empty_task);
pthread_cond_destroy(&pool->empty_task);
pthread_mutex_destroy(&pool->pool_lock);
free(pool->tasks);
free(pool->threads);
free(pool);
}
int main()
{
createThreadpool(3, 20); // 创建线程池
// 创建socket
int lfd = Socket(AF_INET, SOCK_STREAM, 0);
// 端口复用
int opt = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 绑定
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(8888);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
Bind(lfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
// 监听
Listen(lfd, 128);
// 创建epoll对象
gepfd = epoll_create(1);
struct epoll_event events[_EVENT_SIZE_ + 1]; // 用于接收发生的事件
// 添加最初始事件,将监听套接字上树
eventAdd(lfd, EPOLLIN, initAccept, &myevents[_EVENT_SIZE_]);
while (1)
{
int nready = epoll_wait(gepfd, events, 1024, -1);
// 时间参数设置为-1表示永久监听、参数不会返回0
if (nready < 0) // 调用epoll_wait失败
{
perror("epoll_wait error: ");
break;
}
else if (nready > 0)
{
int i = 0;
for (i = 0; i < nready; i++)
{
xevent* xe = events[i].data.ptr; // 取ptr指向结构体地址
if ( (xe->events & events[i].events) && (xe->fd == lfd) ) // 如果是监听套接字,则直接调用回调函数(initAccept),这个过程很快,不需要线程池
xe->call_back(xe->fd, xe->events, xe->arg);
else if (xe->events & events[i].events)
{
Task task;
task.ev = xe;
addTask(task); // 将任务添加给线程池
}
}
}
}
destroy_threadpool(thrPool);
return 0;
}