基于排序链表的定时器(https://blog.csdn.net/destory27/article/details/81748580)存在一个问题:添加定时器的效率偏低。

如图所示时间轮内,指针指向轮子上的一个槽,它以恒定的速度顺时针旋转,每旋转一步就指向先一个槽.该时间轮共有N个槽,旋转一周的时间是N*Si,每个槽指向一条定时器链表,没条链表上的定时器具有相同的特征:它们的定时时间相差N*Si的整数倍.时间轮正是利用这个关系将定时器散列到不同的链表中。假如现在指针指向槽cs,我们要添加一个定时时间为ti的定时器,则该定时器将被插入槽ts对应的链表:     ts = (cs + ti / si) % N.

基于排序链表的定时器使用唯一的一条链表来管理所有的定时器,所有插入操作的效率随着定时器数目的增多而降低.而时间轮使用哈希表思想,将定时器散列到不同的链表上.对于时间轮,要提高定时精度,就要使si值足够小;要提高执行效率,则要求N值足够大.

#ifndef _TIME_WHEEL_TIMER
#define _TIME_WHEEL_TIMER
//时间轮
#include <time.h>
#include <netinet/in.h>
#include <iostream>#define BUFFER_SIZE  64class tw_timer;  //前向声明//绑定socket和定时器
struct client_data{struct sockaddr_in address;  //addrint sockfd;                char buf[BUFFER_SIZE];tw_timer *timer;
};/********定时器类*********/
class tw_timer{public:tw_timer(int rot, int ts):rotation(rot),time_slot(ts){}void (*cb_func)(client_data *);  //定时器回调函数public:int rotation; //时间轮转多少圈后生效int time_slot;     //属于时间轮上哪个槽client_data *user_data{nullptr};   //客户数据tw_timer *next{nullptr};   //指向下一个定时器tw_timer *prev{nullptr};   //指向前一个定时器};class time_wheel{public:time_wheel();~time_wheel();tw_timer *add_timer(int timeout);   //根据定时值timeout创建一个定时器 插入合适的槽中void del_timer(tw_timer *timer);    //删除目标定时器timervoid tick();                       //SI时间到后,调用该函数 时间轮前滚动一个槽的间隔private:static const int N = 60;  //时间轮上槽的数目static const int SI = 1; //槽间隔1Stw_timer *slots[N];    //槽int cur_slot{0}; //当前槽
};time_wheel::time_wheel()
{for(int i = 0; i < N; ++i)slots[i] = nullptr;
}time_wheel::~time_wheel()
{for(int i = 0; i < N; ++i){tw_timer *tmp = slots[i];while(tmp){slots[i] = tmp->next;delete tmp;tmp = slots[i];}}
}//创建定时器
tw_timer* time_wheel::add_timer(int timeout)
{if(timeout < 0)return nullptr;int ticks = 0;//根据插入定时器的超时值计算它将在时间轮多少个滴答后被触发//并将该滴答数存于ticks中if(timeout < SI)ticks = 1;elseticks = timeout / SI;//计算多少圈后被触发int rotation = ticks / N;//计算插入哪个槽中int ts = (cur_slot + (ticks % N)) % N;//创建定时器 在时间轮转动rotation 圈后触发  且位于第ts个槽上 tw_timer *timer = new tw_timer(rotation, ts);if(!slots[ts]){std::cout << "add timer, rotation is " << rotation << " , ts is " << ts << " cur_slot is " << cur_slot << std::endl;slots[ts] = timer;}else{timer->next = slots[ts];slots[ts]->prev = timer;slots[ts] = timer;}return timer;
}//删除目标定时器
void time_wheel::del_timer(tw_timer *timer)
{if(!timer)return;int ts = timer->time_slot;   //在哪个槽上if(timer == slots[ts])   //如果槽中第一个结点是要删除结点{slots[ts] = slots[ts]->next;if(slots[ts])slots[ts]->prev = nullptr;delete timer;}else{timer->prev->next = timer->next;if(timer->next)timer->next->prev = timer->prev;timer->next = nullptr;timer->prev = nullptr;delete timer;}return ;
}//SI时间到后 调用该函数
void time_wheel::tick()
{tw_timer *tmp = slots[cur_slot];  //取得当前槽的头结点std::cout << "Current slot is " << cur_slot << std::endl;while(tmp){std::cout << "tick the timer once" << std::endl;if(tmp->rotation > 0){tmp->rotation--;tmp = tmp->next;}else{tmp->cb_func(tmp->user_data);if(tmp == slots[cur_slot]){std::cout << "delete handle in cur_slot" << std::endl;slots[cur_slot] = tmp->next;delete tmp;if(slots[cur_slot])slots[cur_slot]->prev = nullptr;tmp = slots[cur_slot];}else{tmp->prev->next = tmp->next;if(tmp->next)tmp->next->prev = tmp->prev;tw_timer *tmp2 = tmp->next;delete tmp;tmp = tmp2;}}}cur_slot = ++cur_slot % N;return;
}#endif

服务器测试程序:(epoll)

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <assert.h>
#include <signal.h>
#include <string.h>
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include "TIME_WHEEL_TIMER.hpp"#define FD_LIMIT 65535
#define MAX_EVENT_NUMBER   1024
#define TIMESLOT  1
static int pipefd[2];
static time_wheel tw;
static int epollfd = 0;//设置非阻塞
int setnonblocking(int fd)
{int old_option = fcntl(fd, F_GETFL);int new_option = old_option | O_NONBLOCK;fcntl(fd, F_SETFL, new_option);return old_option;
}void addfd(int epollfd, int fd)
{struct epoll_event event;event.data.fd = fd;event.events = EPOLLIN | EPOLLET;   //ET模式epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);   //添加到事件列表setnonblocking(fd);   //设置非阻塞return ;
}void sig_handler(int sig)
{int save_errno = errno;int msg = sig;send(pipefd[1], (char*)&msg, 1, 0);errno = save_errno;
}//信号处理
void addsig(int sig)
{struct sigaction sa;(struct sigaction *)memset(&sa, '\0', sizeof(struct sigaction));sa.sa_handler = sig_handler;sa.sa_flags |= SA_RESTART;sigfillset(&sa.sa_mask);assert(sigaction(sig, &sa, NULL) != -1);return ;
}//
void timer_handler()
{tw.tick();alarm(TIMESLOT);
}//回调函数
void cb_func(client_data *user_data)
{epoll_ctl(epollfd, EPOLL_CTL_DEL, user_data->sockfd, 0);assert(user_data);close(user_data->sockfd);std::cout << "close fd " << user_data->sockfd << std::endl;
}int main(int argc, char **argv)
{if(argc <= 2)   //使用IP port{std::cout << "usage:" << argv[0] << " ipaddr port" << std::endl;return -1;}struct sockaddr_in address;bzero(&address, sizeof(struct sockaddr_in));address.sin_family = AF_INET;inet_pton(AF_INET, argv[1], &address.sin_addr);address.sin_port = htons(atoi(argv[2]));//socketint listenfd = socket(PF_INET, SOCK_STREAM, 0);assert(listenfd != -1);int reuse = 1;setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));//bindint ret = bind(listenfd, (struct sockaddr*)&address, sizeof(struct sockaddr_in));assert(ret != -1);//listenret = listen(listenfd, 5);assert(ret != -1);//事件集合struct epoll_event events[MAX_EVENT_NUMBER];//列表套接字int epollfd = epoll_create(5);assert(epollfd != -1);addfd(epollfd, listenfd);  //添加到事件列表//双向通信ret = socketpair(PF_UNIX, SOCK_STREAM, 0, pipefd);assert(ret != -1);setnonblocking(pipefd[1]);addfd(epollfd, pipefd[0]);//设置信号处理函数addsig(SIGALRM);addsig(SIGTERM);bool stop_server = false;client_data *users = new client_data[FD_LIMIT];bool timeout = false;alarm(TIMESLOT);    //定时while(!stop_server){int num = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);if(num < 0 && errno != EINTR){std::cout << "epoll failure" << std::endl;break;}for(int i = 0; i < num; ++i){int sockfd = events[i].data.fd;if(sockfd == listenfd)   //新的连接请求{struct sockaddr_in client;socklen_t len = sizeof(struct sockaddr_in);int connfd = accept(listenfd, (struct sockaddr*)&client, &len);assert(connfd != -1);addfd(epollfd, connfd);users[connfd].address = client;users[connfd].sockfd = connfd;//创建定时器tw_timer *timer = tw.add_timer(5);timer->user_data = &users[connfd];timer->cb_func = cb_func;      //回调函数users[connfd].timer = timer;break;}else if(sockfd == pipefd[0] && events[i].events & EPOLLIN)  //处理信号{int sig;char signals[1024];ret = recv(pipefd[0], signals, sizeof(signals), 0);assert(ret != -1);if(ret == 0)continue;else{for(int i = 0; i < ret; ++i)switch(signals[i]){case SIGALRM:timeout = true; //有定时任务需要处理break;case SIGTERM:stop_server = true;}}}else if(events[i].events & EPOLLIN)  //处理客户连接上接收的数据{memset(users[sockfd].buf, '\0', BUFFER_SIZE);ret = recv(sockfd, users[sockfd].buf, BUFFER_SIZE - 1, 0);std::cout << "get " << ret << " bytes os client data " << users[sockfd].buf << " from " << sockfd << std::endl;tw_timer *timer = users[sockfd].timer;if(ret < 0)          //删除定时器  关闭描述符{if(errno != EAGAIN){cb_func(&users[sockfd]);if(timer)tw.del_timer(timer);}}else if(ret == 0){cb_func(&users[sockfd]);if(timer)tw.del_timer(timer);}else{//调整定时器 延迟关闭if(timer){time_t cur = time(NULL);timer->rotation = 1;std::cout << "adjust timer once" << std::endl;}}}else{;}}if(timeout){timer_handler();timeout = false;}}close(listenfd);close(pipefd[1]);close(pipefd[0]);delete []users;return 0;
}

客户端可使用telnet测试。

高性能定时器-------时间轮相关推荐

  1. 高性能定时器--时间轮/多级时间轮

    运行原理 指针指向轮子上的一个槽,轮子以恒定的速度顺时针转动,每转动一步就指向下一个槽(虚线指针指向的槽),每次转动称为一个tick,一个tick的时间称为时间轮的槽间隔slot interval,即 ...

  2. 游戏后台之高效定时器-时间轮

    高性能定时器 定时器的结构有多钟比如链表式,最小堆,时间轮的 在不同应用场景下使用哪种需要考虑效率和复杂度 这次我么那先先讲讲时间轮定时器,在linux内核里这种结构的定时器大量使用. 1.升序链表定 ...

  3. 心跳与超时:高并发高性能的时间轮超时器

    在许多业务场景中,我们都会碰到延迟任务,定时任务这种需求.特别的,在网络连接的场景中,常常会出现一些超时控制.由于服务端的连接数量很大,这些超时任务的数量往往也是很庞大的.实现对大量任务的超时管理并不 ...

  4. 时间轮和时间堆管理定时器

    高性能定时器 时间轮 由于排序链表定时器容器有这样一个问题:添加定时器的效率偏低.而即将介绍的时间轮则解决了这个问题.一种简单的时间轮如下所示. 如图所示的时间轮内,指针指向轮子上的一个slot(槽) ...

  5. Linux网络编程 | 高性能定时器 :时间轮、时间堆

    文章目录 时间轮 时间堆 在上一篇博客中我实现了一个基于排序链表的定时器容器,但是其存在一个缺点--随着定时器越来越多,添加定时器的效率也会越来越低. 而下面的两个高效定时器--时间轮.时间堆,会完美 ...

  6. 时间轮 (史上最全)

    缓存之王 Caffeine 中,涉及到100w级.1000W级.甚至亿级元素的过期问题,如何进行高性能的定时调度,是一个难题. 注: 本文从 对 海量调度任务场景中, 高性能的时间轮算法, 做了一个 ...

  7. 高性能定时器2——红黑树实现

    ​ 在网络程序中我们通常要处理三种事件,网络I/O事件.信号以及定时事件,我们可以使用I/O复用系统调用(select.poll.epoll)将这三类事件进行统一处理.我们通常使用定时器来检测一个客户 ...

  8. 如何快速实现分布式定时器丨红黑树|跳表|堆|时间轮|缓存|锁|事务|架构|高性能|消息队列丨C/C++Linux服务器开发丨C++后端开发

    如何快速实现分布式定时器 视频讲解如下,点击观看: 如何快速实现分布式定时器丨红黑树|跳表|堆|时间轮|缓存|锁|事务|架构|高性能|消息队列丨C/C++Linux服务器开发丨C++后端开发丨中间件 ...

  9. 网络编程 高性能定时器数据结构分析 | 时间轮 红黑树定时器性能分析 | 为什么要做用户态定时器

    为什么要用户态的定时器? 首先是为什么要做定时器,定时器的主要说的是我们的应用(业务?功能?总之有这个需求)要做一个定时的任务.其实如果不想为什么,好像是理所当然的.我写这个的时候,知乎有一个问题(L ...

最新文章

  1. python读取txt文件并画图
  2. Kafka-0.10.0.0 集群高可靠实验
  3. 浙大机器鱼登Nature封面!22cm身段,探索地球最深海沟
  4. Hadoop 学习笔记 (十一) MapReduce 求平均成绩
  5. 如何保证战略落地_战略如何规划落地?值得借鉴
  6. C语言程序设计:现代方法 中文高清PDF版下载
  7. webpack-dev-server启动后, localhost:8080返回index.html的原理
  8. 图书信息管理系统需求分析
  9. bp神经网络算法原理公式,bp神经网络算法推导
  10. 使用SVG画一个罗盘
  11. 数学建模:R语言的正态性检验
  12. java 苹果cms 萌果_MacCMS8.x(苹果CMS8.x)整合Ckplayer6.4
  13. html模糊遮罩层磨砂玻璃,常见的PPT背景:如何设计PPT背景?
  14. 人物拼图java_JAVA实现拼图游戏
  15. 卷积神经网路之感受野(receptive field)的理解
  16. 绿色版Mysql的安装配置
  17. 实现数据库存入html代码,并在前端就页面返回。
  18. python计算时间差(排除非工作日)
  19. SpringBoot访问jar包静态文件
  20. Java中的DO,DTO,VO,POJO

热门文章

  1. 201819102023王逢千禧
  2. dfs算法详解(n皇问题实现)
  3. 素问·六节藏象论原文
  4. IE使用滤镜实现渐变
  5. (转)走进JVM,浅水也能捉鱼
  6. 阿里巴巴是怎么成为“税王”的?
  7. C#中文和英文字符串长度问题
  8. Html+Css+jQuery悬浮菜单
  9. Bootstrap响应式框架,组件化开发
  10. 云计算技术与应用 -基础概念与分布式计算