epoll+线程池模型

devtools/2024/9/26 1:05:53/

🔥博客主页: 我要成为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个线程,一个生产者,十个消费者,一个管理者,一个输出线程


http://www.ppmy.cn/devtools/100035.html

相关文章

索引器+重载运算符

索引器 索引器类似于属性&#xff0c;可以让类的成员被快速访问&#xff0c;使用一个或多个参数可以索引到类中的成员 。 声明 访问修饰符 数据类型 this[数据类型 索引参数] {get{return} //读set{字段value} //写 } 使用&#xff1a;对象名[索引参数] 如何在函数中抛…

RUST知识框架与学习框架

RUST知识框架与学习框架 Rust是一门系统级编程语言&#xff0c;以其内存安全、高性能和并发性而著称&#xff0c;特别适用于开发低级系统、网络服务和并发应用&#xff0c;具有广泛应用前景。 一、Rust知识框架 基础语法与核心概念 变量与类型&#xff1a;包括基本数据类型、…

通过VIN车架号查询车辆登记日期

我们先来介绍下什么是vin码&#xff0c;以及vin码的构成结构解析&#xff0c;汽车VIN码&#xff0c;也叫车辆识别号码&#xff0c;通俗可以理解为汽车的身份证号码。 接口介绍 通过17位vin码&#xff0c;获取到车辆的发动机号&#xff0c;初登日期&#xff0c;车辆型号等信息。…

简单实现进度条效果(vue2)

如果用echarts或者其他图表来写个进度条有点大材小用&#xff0c;所以直接简单html、js写一下就可以&#xff1b; 以下代码基于vue2&#xff0c; 部分代码来自国内直连GPT/Claude镜像站 <template><div class"progress-container"><div class"p…

Leetcode 3266. Final Array State After K Multiplication Operations II

Leetcode 3266. Final Array State After K Multiplication Operations II 1. 解题思路2. 代码实现 题目链接&#xff1a;3266. Final Array State After K Multiplication Operations II 1. 解题思路 这一题是题目3264. Final Array State After K Multiplication Operatio…

书生模型实战L1---OpenCompass 评测

书生模型实战系列文章目录 第一章 入门岛L0&#xff08;Linux&#xff09; 第二章 入门岛L0&#xff08;python&#xff09; 第三章 入门岛L0&#xff08;Git&#xff09; 第四章 基础岛L1&#xff08;书生全链路开源介绍&#xff09; 第五章 基础岛L1&#xff08;Demo&#x…

mybatis框架搭建、mybatis打印日志设置、参数传递使用、myatis插件MyBatisX

一、框架 就是对技术的封装&#xff0c;将基础的技术进行封装&#xff0c;让程序员可以快速的使用&#xff0c;提高效率。 Java后端框架&#xff1a; mybatis&#xff1a;对jdbc进行封装 spring&#xff1a;对整个Java后端架构进行管理的 springweb&#xff1a;对web层&a…

Agentic Security:一款针对LLM模型的模糊测试与安全检测工具

关于Agentic Security Agentic Security是一款针对LLM模型的模糊测试与安全检测工具&#xff0c;该工具可以帮助广大研究人员针对任意LLM执行全面的安全分析与测试。 请注意 Agentic Security 是作为安全扫描工具设计的&#xff0c;而不是万无一失的解决方案。它无法保证完全防…