epoll+线程池模型

2024-08-25 22:52
文章标签 线程 模型 epoll

本文主要是介绍epoll+线程池模型,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

🔥博客主页: 我要成为C++领域大神
🎥系列专栏:【C++核心编程】 【计算机网络】 【Linux编程】 【操作系统】
❤️感谢大家点赞👍收藏⭐评论✍️

本博客致力于知识分享,与更多的人进行学习交流

负载均衡技术

大量的用户请求可能导致任务分发不均匀,导致资源浪费,不能很好的处理和响应

通过预先设定的分发策略,最大的尝试均匀分发业务,让每台处理机都有任务负载

代理服务器

代理服务器是一个以数据中转为主要职责的中间件,代理服务器可以将用户的请求中转给处理服务器机,也可以将结果反馈给用户,避免用户直接访问服务器主机,提高安全性(安全策略都可以部署在代理服务器中),还可以进行任务的控制与分发,例如负载均衡可以在代理服务器中完成

HA高可用性结构

某个处理机宕机,可用通过HA概念将数据任务转发给正常的处理机。察觉处理机异常,快速反应

线程池设计原则

epoll具有强大的socket监听能力,可以快速察觉所有套接字,线程池具有高并发处理能力,大量的用户请求可用快速处理。

1)提高线程重用性,线程不能与用户绑定,可重复为多个用户处理业务,避免频繁创建销毁线程,减少不必要的开销。

2)预创建,提前准备好部分线程待用,用户发送请求后直接选择线程处理,提高响应速度

3)线程管理策略,设定线程池阈值,通过阈值管理调度线程,线程扩容与缩减

4)为服务器提供并发处理能力,可以更快处理请求或业务

5)提高线程池的重用性,用户实现任务,线程池负责执行任务

6)线程池使用生产者消费者实现,任务传递模式

工作流程

线程池:

生产者将要处理的业务传递到任务队列中去,如果任务队列中有可获取的任务,消费者一直获取执行,生产者投递的任务不允许持续占用消费者。管理者会对消费者进行扫描,根据阈值检测是否需要扩容和缩减,对消费者进行创建或者杀死。

epoll模型:

生产者负责实现epoll模型,将事件转换成业务,投递到线程池中

线程池的扩容和缩减

使用线程池最小阈值min,作为扩容增减数量

扩容:当前任务量>=闲消费者数量 或者 忙线程数量占活线程数量的70%

缩减:当前线程数量+扩容量,小于最大线程阈值

消费者与管理者配合实现缩减

epoll的水平触发模式和边缘触发模式

epoll 的水平触发(Level Triggered, LT)和边缘触发(Edge Triggered, ET)是两种不同的事件通知机制,它们定义了 epoll 如何向应用程序报告文件描述符上的事件。


水平触发(LT)

在水平触发模式下,只要满足条件的事件仍然存在,epoll 就会重复通知这个事件。比如,如果一个文件描述符上有可读数据,那么只要没有读完,epoll_wait 就会不断报告该文件描述符是可读的。这种模式的特点是:

容错性较好,不易丢失事件。

更易于编程和理解。

可以用于多线程程序中,多个线程可以共享同一个 epoll 文件描述符。

缺点:开销大,往返在socket缓冲区和用户之间

在水平模式下,我们的epoll+线程池模型有问题,当第一轮事件监听未处理完毕,epoll_wait不会阻塞,当再次有客户端发送任务时epoll_wait立即返回,并且会对任务进行误添加。

可以以边缘触发模式监听socket的读事件来避免这种问题,node.events=EPOLL | EPOLLET


边缘触发(ET)

边缘触发模式下,事件只在状态变化时被通知一次,之后即使条件仍然满足,也不会再次通知,直到状态再次发生变化。例如,只有当新数据到达使得文件描述符从非可读变为可读时,epoll_wait 才会报告可读事件。边缘触发模式的特点是:

效率更高,因为它减少了事件的重复通知。

需要更加小心地处理每次通知,确保处理所有的数据,否则可能会丢失未处理完的数据。

更适合单线程或者每个线程使用独立 epoll 文件描述符的场景。

代码实现

文件结构:

makefile:

server.h

#ifndef __server_H__
#define __server_H__#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>#define TIMEOUT 1
pthread_mutex_t lock;typedef struct
{void *(*bussiness)(void *);//任务函数指针void *arg;}bs_t;typedef struct
{int thread_shutdown;//线程池开关int thread_max;//最大线程数int thread_min;//最小线程数int thread_alive;//有效线程数int thread_busy;//忙线程数量int kill_number;//缩减码bs_t *queue;//任务队列int front;int rear;int cur;int max;pthread_cond_t Not_Full;pthread_cond_t Not_Empty;pthread_t * ctids;//存储消费者IDpthread_t mtid;//存储管理者ID
}pool_t;//线程池类型pool_t * thread_pool_create(int Max,int Min,int Qmax);//线程池初始化
int Producer_add_task(pool_t * p,bs_t bs);//生产者添加任务模块,执行一次添加一次任务
void *Customer_thread(void *arg);//消费者线程,参数为线程池地址
void *Manager_thread(void *arg);//管理者线程,参数为线程池地址
int thread_pool_destroy(pool_t *p);//销毁线程池
void * user_bussiness(void *arg);//自定义线程任务
int is_thread_alive(pthread_t pid);

thread_pool_create.c

#include "../include/server.h"pool_t * thread_pool_create(int Max,int Min,int Qmax)
{pool_t *ptr=NULL;if((ptr=(pool_t*)malloc(sizeof(pool_t)))==NULL){perror("thread_pool_create malloc pool failed");exit(0);}ptr->thread_shutdown=1;ptr->thread_max=Max;ptr->thread_min=Min;ptr->thread_alive=0;ptr->kill_number=0;ptr->thread_busy=0;if((ptr->queue=(bs_t*)malloc(sizeof(bs_t)*Qmax))==NULL){perror("thread_pool_create malloc queue failed");exit(0);}if((ptr->ctids=(pthread_t*)malloc(sizeof(pthread_t)*Max))==NULL){perror("thread_pool_create malloc ctids failed");exit(0);}ptr->front=0;ptr->rear=0;ptr->cur=0;ptr->max=Qmax;if(pthread_cond_init(&ptr->Not_Full,NULL)!=0 || pthread_cond_init(&ptr->Not_Empty,NULL)!=0|| pthread_mutex_init(&lock,NULL)!=0){printf("thread pool create failed,init Cond or Lock Failed\n");exit(0);}int err;for(int i=0;i<Min;++i){if((err=pthread_create(&ptr->ctids[i],NULL,Customer_thread,(void*)ptr))!=0){printf("thread pool create failed,customer thread create failed:%s",strerror(err));exit(0);}++ptr->thread_alive;}if((err=pthread_create(&ptr->mtid,NULL,Manager_thread,(void*)ptr))!=0){printf("thread pool create failed,manager thread create failed:%s",strerror(err));exit(0);}pthread_create(&ptid,NULL,print_thread,(void*)ptr);printf("Print Thread Create Success...\n");return ptr;
}

thread_pool_destroy.c

#include "../include/server.h"int thread_pool_destroy(pool_t *p)
{pthread_mutex_destroy(&lock);pthread_cond_destroy(&p->Not_Full);pthread_cond_destroy(&p->Not_Empty);free(p->ctids);free(p->queue);free(p);return 0;
}

Epoll_Listen.c

#include "../include/server.h"int Epoll_Listen(int serverfd,pool_t * p)
{struct epoll_event ready_array[EPOLLMAX];int ready,flag;bs_t tmp;printf("Epoll_Thread Server,Epoll_Listen Running...\n");while(p->thread_shutdown){if((ready=epoll_wait(epfd,ready_array,EPOLLMAX,-1))==-1){perror("Epoll_Listen call failed,epoll_wait call failed");exit(0);}flag=0;while(ready){if(ready_array[flag].data.fd==serverfd){tmp.business=Business_Accept;tmp.arg=(void *)&serverfd;Producer_add_task(p,tmp);}else{tmp.business=Business_Retime;tmp.arg=((void*)&ready_array[flag].data.fd);Producer_add_task(p,tmp);}++flag;--ready;}}close(serverfd);return 0;
}

Manager_thread.c

#include "../include/server.h"void * Manager_thread(void *arg)
{pthread_detach(pthread_self());pool_t *p=(pool_t *)arg;int alive,cur,busy;pthread_mutex_lock(&lock);alive=p->thread_alive;busy=p->thread_busy;cur=p->cur;pthread_mutex_unlock(&lock);int add,flag;//持续执行while(p->thread_shutdown){if((cur>=alive-busy||(double)busy/alive*100>=70)&&alive+p->thread_min<=p->thread_max){for(flag=0,add=0;flag<p->thread_max&&add<p->thread_min;flag++){if(p->ctids[flag]==0 || !is_thread_alive(p->ctids[flag])){pthread_create(&p->ctids[flag],NULL,Customer_thread,(void*)p);add++;pthread_mutex_lock(&lock);++(p->thread_alive);pthread_mutex_unlock(&lock);}}pthread_kill(ptid,SIGUSR1);}if(busy*2<=alive-busy && alive-p->thread_min >= p->thread_min){printf("%d\n",p->thread_min);pthread_mutex_lock(&lock);p->kill_number=p->thread_min;pthread_mutex_unlock(&lock);for(int i=0;i<p->thread_min;++i){pthread_cond_signal(&p->Not_Empty);}}sleep(TIMEOUT);}printf("Thread shutdown 0,manager thread[0x%x]exiting...\n",(unsigned int)pthread_self());pthread_exit(NULL);
}

Business_Retime.c

#include "../include/server.h"void * Business_Retime(void *arg)
{int toupper_flag=0;char recv_buf[1024];bzero(recv_buf,sizeof(recv_buf));char time_buf[100];bzero(time_buf,sizeof(time_buf));time_t tp;int recvlen;int sockfd=*(int*)arg;printf("111\n");while((recvlen=recv(sockfd,recv_buf,sizeof(recv_buf),MSG_DONTWAIT))==-1){if(errno==EINTR)break;perror("Business_Retime recv call failed");exit(0);}if(recvlen>0){if (strcmp(recv_buf, "localtime") == 0) {tp = time(NULL); // 获取时间种子ctime_r(&tp, time_buf);time_buf[strcspn(time_buf, "\n")] = '\0';send(sockfd, time_buf, strlen(time_buf) + 1, MSG_NOSIGNAL);bzero(time_buf, sizeof(time_buf));} else {toupper_flag = 0;while (recvlen > toupper_flag) {recv_buf[toupper_flag] = toupper(recv_buf[toupper_flag]);++toupper_flag;}send(sockfd, recv_buf, recvlen, MSG_NOSIGNAL);bzero(recv_buf, sizeof(recv_buf));}}else if(recvlen==0){close(sockfd);epoll_ctl(epfd,EPOLL_CTL_DEL,sockfd,NULL);}return NULL;
}

Epoll_Create.c

#include "../include/server.h"int Epoll_Create(int serverfd)
{int epfd;if((epfd=epoll_create(EPOLLMAX))==-1){perror("Epoll_Create call failed,epoll_create call failed");exit(0);}struct epoll_event node;node.data.fd=serverfd;node.events=EPOLLIN|EPOLLET;if((epoll_ctl(epfd,EPOLL_CTL_ADD,serverfd,&node))==-1){perror("Epoll_Create call failed,epoll_ctl call failed");exit(0);}printf("Epoll_Server Epoll Create success...\n");return epfd;}

Business_Accept.c

#include "../include/server.h"void *Business_Accept(void *arg)
{struct sockaddr_in addr;socklen_t addrlen;int sockfd=*(int *)arg;int customerfd;struct epoll_event node;char response[1024];char ip[16];bzero(response,sizeof(response));bzero(ip,sizeof(ip));addrlen=sizeof(addr);if((customerfd=accept(sockfd,(struct sockaddr*)&addr,&addrlen))==-1){perror("Business_Accept accept call failed");exit(0);}inet_ntop(AF_INET,&addr.sin_addr.s_addr,ip,16);node.data.fd=customerfd;node.events=EPOLLIN|EPOLLET;if(epoll_ctl(epfd,EPOLL_CTL_ADD,customerfd,&node)==-1){perror("Business_Accept epoll_ctl call failed");exit(0);}sprintf(response,"hi Thread [%s] welcome to epoll demo",ip);send(customerfd,response,strlen(response),MSG_NOSIGNAL);return NULL;
}

print_thread.c

#include "../include/server.h"void *print_thread(void*arg)
{pthread_detach(pthread_self());pool_t *ptr=(pool_t*)arg;PTR=ptr;struct sigaction act,oact;act.sa_handler=sig_usr;act.sa_flags=0;sigemptyset(&act.sa_mask);sigaction(SIGUSR1,&act,&oact);//设置捕捉sigprocmask(SIG_SETMASK,&act.sa_mask,NULL);//解除屏蔽while(ptr->thread_shutdown)sleep(TIMEOUT);//等待信号pthread_exit(NULL);
}

sig_usr.c

#include "../include/server.h"void sig_usr(int n)
{//显示一次阈值信息printf("[Thread_Epoll_Server Info] alive[%d] busy[%d] Idel[%d] Cur[%d] Busy/Alive[%.2f%%] Alive/max[%.2f%%]\n",
PTR->thread_alive,PTR->thread_busy,PTR->thread_alive-PTR->thread_busy,PTR->cur,(double)PTR->thread_busy/PTR->thread_alive*100,(double)PTR->thread_alive/PTR->thread_max*100);
} 

Producer_add_task.c

#include "../include/server.h"int Producer_add_task(pool_t *p,bs_t bs)
{if(p->thread_shutdown){//上锁pthread_mutex_lock(&lock);while(p->cur==p->max){pthread_cond_wait(&p->Not_Full,&lock);if(!p->thread_shutdown){pthread_mutex_unlock(&lock);printf("thread shutdown 0,exiting...\n");pthread_exit(NULL);}}//添加一个业务p->queue[p->front].business=bs.business;p->queue[p->front].arg=bs.arg;++p->cur;p->front=(p->front+1)%p->max;//解锁pthread_mutex_unlock(&lock);pthread_kill(ptid,SIGUSR1);//唤醒一个消费者pthread_cond_signal(&p->Not_Empty);}else{printf("thread shutdown 0,exiting...\n");pthread_exit(NULL);}printf("Producer Thread [0x%x] Add Task Successfully,business_addr=%p\n",(unsigned int)pthread_self(),bs.business);return 0;
}

Net_init.c

#include "../include/server.h"int Net_init(void)
{int sockfd;struct sockaddr_in sockAddr;bzero(&sockAddr,sizeof(sockAddr));sockAddr.sin_family=AF_INET;sockAddr.sin_port=htons(8080);sockAddr.sin_addr.s_addr=htonl(INADDR_ANY);if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1){perror("socket call failed");exit(0);}if(bind(sockfd,(struct sockaddr*)&sockAddr,sizeof(sockAddr))==-1){perror("bind call failed");exit(0);}listen(sockfd,BACKLOG);printf("Epoll_thread Server Net init Success...\n");return sockfd;
}

is_thread_alive.c

#include "../include/server.h"int is_thread_alive(pthread_t tid)
{pthread_kill(tid,0);if(errno==ESRCH)return 0;return 1;}

main.c

#include "../include/server.h"int main(void)
{//主线程设置对SIGUSR1信号的屏蔽,继承给所有线程sigset_t set,oset;sigemptyset(&set);sigaddset(&set,SIGUSR1);sigprocmask(SIG_SETMASK,&set,&oset);//启动接口startint sockfd=Net_init();epfd=Epoll_Create(sockfd);pool_t *ptr=thread_pool_create(300,10,1000);Epoll_Listen(sockfd,ptr);if(!ptr->thread_shutdown) thread_pool_destroy(ptr);printf("Epoll Server Closing...\n");return 0;
}

运行结果

在服务端刚启动后,有13个线程,一个生产者,十个消费者,一个管理者,一个输出线程

这篇关于epoll+线程池模型的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/1106875

相关文章

Java 线程安全与 volatile与单例模式问题及解决方案

《Java线程安全与volatile与单例模式问题及解决方案》文章主要讲解线程安全问题的五个成因(调度随机、变量修改、非原子操作、内存可见性、指令重排序)及解决方案,强调使用volatile关键字... 目录什么是线程安全线程安全问题的产生与解决方案线程的调度是随机的多个线程对同一个变量进行修改线程的修改操

Java中实现线程的创建和启动的方法

《Java中实现线程的创建和启动的方法》在Java中,实现线程的创建和启动是两个不同但紧密相关的概念,理解为什么要启动线程(调用start()方法)而非直接调用run()方法,是掌握多线程编程的关键,... 目录1. 线程的生命周期2. start() vs run() 的本质区别3. 为什么必须通过 st

Linux实现线程同步的多种方式汇总

《Linux实现线程同步的多种方式汇总》本文详细介绍了Linux下线程同步的多种方法,包括互斥锁、自旋锁、信号量以及它们的使用示例,通过这些同步机制,可以解决线程安全问题,防止资源竞争导致的错误,示例... 目录什么是线程同步?一、互斥锁(单人洗手间规则)适用场景:特点:二、条件变量(咖啡厅取餐系统)工作流

Java中常见队列举例详解(非线程安全)

《Java中常见队列举例详解(非线程安全)》队列用于模拟队列这种数据结构,队列通常是指先进先出的容器,:本文主要介绍Java中常见队列(非线程安全)的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录一.队列定义 二.常见接口 三.常见实现类3.1 ArrayDeque3.1.1 实现原理3.1.2

SpringBoot3中使用虚拟线程的完整步骤

《SpringBoot3中使用虚拟线程的完整步骤》在SpringBoot3中使用Java21+的虚拟线程(VirtualThreads)可以显著提升I/O密集型应用的并发能力,这篇文章为大家介绍了详细... 目录1. 环境准备2. 配置虚拟线程方式一:全局启用虚拟线程(Tomcat/Jetty)方式二:异步

如何解决Druid线程池Cause:java.sql.SQLRecoverableException:IO错误:Socket read timed out的问题

《如何解决Druid线程池Cause:java.sql.SQLRecoverableException:IO错误:Socketreadtimedout的问题》:本文主要介绍解决Druid线程... 目录异常信息触发场景找到版本发布更新的说明从版本更新信息可以看到该默认逻辑已经去除总结异常信息触发场景复

详解如何使用Python从零开始构建文本统计模型

《详解如何使用Python从零开始构建文本统计模型》在自然语言处理领域,词汇表构建是文本预处理的关键环节,本文通过Python代码实践,演示如何从原始文本中提取多尺度特征,并通过动态调整机制构建更精确... 目录一、项目背景与核心思想二、核心代码解析1. 数据加载与预处理2. 多尺度字符统计3. 统计结果可

SpringBoot整合Sa-Token实现RBAC权限模型的过程解析

《SpringBoot整合Sa-Token实现RBAC权限模型的过程解析》:本文主要介绍SpringBoot整合Sa-Token实现RBAC权限模型的过程解析,本文给大家介绍的非常详细,对大家的学... 目录前言一、基础概念1.1 RBAC模型核心概念1.2 Sa-Token核心功能1.3 环境准备二、表结

JAVA保证HashMap线程安全的几种方式

《JAVA保证HashMap线程安全的几种方式》HashMap是线程不安全的,这意味着如果多个线程并发地访问和修改同一个HashMap实例,可能会导致数据不一致和其他线程安全问题,本文主要介绍了JAV... 目录1. 使用 Collections.synchronizedMap2. 使用 Concurren

Spring Security基于数据库的ABAC属性权限模型实战开发教程

《SpringSecurity基于数据库的ABAC属性权限模型实战开发教程》:本文主要介绍SpringSecurity基于数据库的ABAC属性权限模型实战开发教程,本文给大家介绍的非常详细,对大... 目录1. 前言2. 权限决策依据RBACABAC综合对比3. 数据库表结构说明4. 实战开始5. MyBA