EPoll反应堆+线程池简单示例

线程池的作用是为了避免重复创建销毁线程,这篇文章在上一篇的基础上加上了线程池。

反应堆代码在上一篇文章中:epoll反应堆-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/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;
}

  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Epoll+线程池的工作原理如下: 1. 单线程创建epoll并等待,有I/O请求(socket)到达时,将其加入epoll并从线程池中取一个空闲工作者线程,将实际的业务交由工作者线程处理。 2. 当多个任务到来时,Epoll及时响应并将任务下发给特定的处理线程,完成对应的任务。 3. 如果只用单线程进行listen轮询监听的话,效率上实在是太低。而借助epoll的话就会很完美的解决这个问题。 4. 使用线程池的缘由是为了避免频繁创建和销毁线程,提高线程的复用率和效率。 示例: ```python import socket import threading import queue import select # 定义线程池类 class ThreadPool: def __init__(self, max_workers): self.max_workers = max_workers self._workers = [] self._task_queue = queue.Queue() self._init_workers() # 初始化线程池 def _init_workers(self): for i in range(self.max_workers): worker = threading.Thread(target=self._worker) worker.start() self._workers.append(worker) # 工作者线程 def _worker(self): while True: try: func, args, kwargs = self._task_queue.get() func(*args, **kwargs) except Exception as e: print(e) # 提交任务 def submit(self, func, *args, **kwargs): self._task_queue.put((func, args, kwargs)) # 定义服务端类 class Server: def __init__(self, host, port, max_workers): self.host = host self.port = port self.max_workers = max_workers self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.server_socket.bind((self.host, self.port)) self.server_socket.listen(5) self.thread_pool = ThreadPool(self.max_workers) # 处理客户端请求 def handle_request(self, client_socket, client_address): print(f"Connected by {client_address}") while True: data = client_socket.recv(1024) if not data: break client_socket.sendall(data) client_socket.close() # 运行服务端 def serve_forever(self): print(f"Server is running on {self.host}:{self.port}") while True: client_socket, client_address = self.server_socket.accept() self.thread_pool.submit(self.handle_request, client_socket, client_address) # 运行服务端 if __name__ == '__main__': server = Server('localhost', 8888, 10) server.serve_forever() ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值