总结定时器、时间轮
发布日期:2021-09-12 06:44:53 浏览次数:2 分类:技术文章

本文共 8947 字,大约阅读时间需要 29 分钟。

先占坑,总结到目前为止的所学和观点,留待日后学到新的知识或者有新的认知了再回来更新


(先吐槽,网上搜定时器和时间轮,那些博客的代码居然基本上都是Linux高性能上的,虽然我也是…感叹一下看来这本书很厉害,是不是目前讲这方面的书很少,只有它啊…)


  • 定时器
    定时器就是一个数据结构,他最主要的成员是回调函数和超时时间,等到超时时间到期,自动执行这个函数。
  • 定时器的实现
    • 单线程实现
      单线程实现类似于在一个循环线程里用epoll和usleep等待接口实现超时回调
    • 多线程实现
      /* 日后再说 */
  • 时间轮
    本质就是一个定时器容器,可以更具效率的管理定时器,下面贴的代码也是出自《Linux高性能》,以数组实现了一个时间轮,只有一个”轮子“

(1)定时器的简单实现

//升序定时器链表#ifndef LST_TIMER_H#define LST_TIMER_H#include 
#include
#include
#define BUFFER_SIZE 64class util_timer; //前向声明struct client_data { struct sockaddr_in address; int sockfd; char buf[BUFFER_SIZE]; util_timer* timer;};//定时器类class util_timer {public: util_timer() : prev(nullptr), next(nullptr) {} time_t expire; //任务的超时时间,这里使用绝对时间 void (*cb_func) (client_data*); //函数指针 任务回调函数 //回调函数处理的客户数据,由定时器的执行者传递给回调函数 client_data* user_data; util_timer* prev; //指向前一个定时器 util_timer* next; //指向下一个定时器};//定时器链表。它是一个升序、双向链表,且带有头结点和尾节点class sort_timer_lst { util_timer* head; util_timer* tail; //一个重载的辅助函数,他被公有的add_timer函数和adjust_timer函数调用。 //该函数表示将目标定时器timer添加到节点lst_head之后的部分链表 void add_timer(util_timer* timer, util_timer* lst_head);public: sort_timer_lst() : head(nullptr), tail(nullptr) {} //链表被销毁时,删除其中所有的定时器 ~sort_timer_lst(); //将目标定时器timer添加到链表中 void add_timer(util_timer* timer); //当某个定时任务发生变化时,调整对应的定时器在链表中的位置。这个函数 //只考虑被调整的定时器的超时时间延长的情况,即该定时器需要往链表的尾部移动 void adjust_timer(util_timer* timer); //将目标定时器从链表中删除 void del_timer(util_timer* timer); //SIGALRM信号每次被触发就在其信号处理函数(如果使用统一事件源,则是主函数) //中执行一次tick函数,已处理链表上到期的任务 void tick();};sort_timer_lst::~sort_timer_lst() { util_timer* tmp = head; while (tmp) { head = tmp->next; delete tmp; tmp = head; }}void sort_timer_lst::add_timer(util_timer* timer) { if (!timer) { return; } if (!head) //若果链表空 { head = tail = timer; return; } //如果目标定时器的超时时间小于当前链表中所有定时器的超时时间 //则把该定时器插入链表头部,作为新的链表头结点。否则就需要重载函数 //add_timer(util_timer* timer, util_timer* lst_head),把他 //插入链表中合适的位置,以保证链表的升序特性 if (timer->expire < head->expire) { timer->next = head; head->prev = timer; head = timer; return ; } add_timer(timer, head);}void sort_timer_lst::adjust_timer(util_timer* timer) { if (!timer) { return; } util_timer* tmp = timer->next; //如果被调整的目标定时器在链表尾部,或者该定时器新的超时值仍然小于其下一个 //定时器的超时值,则不用调整 if (!tmp || (timer->expire < tmp->expire)) { return ; } //如果目标定时器是链表的头节点,则将该定时器从链表中取出并重新插入链表 if (timer == head) { head = head->next; head->prev = nullptr; timer->next = nullptr; add_timer(timer, head); } //如果目标定时器不是链表头节点,则将该定时器从链表中取出,然后插入其原来所在位置之后的部分链表中 else { timer->prev->next = timer->next; timer->next->prev = timer->prev; add_timer(timer, timer->next); }}void sort_timer_lst::del_timer(util_timer* timer) { if (!timer) { return ; } //下面这个条件成立表示链表中只有一个定时器,即目标定时器 if ((timer == head) && (timer == tail)) { delete timer; head = nullptr; tail = nullptr; return ; } //如果链表中至少有两个定时器,且目标定时器是链表的头节点,则将链表的头节点 //重置为原头节点的下一个节点 if (timer == head) { head = head->next; head->prev = nullptr; delete timer; return ; } //如果是尾节点 if (timer == tail) { tail->prev->next = nullptr; tail = tail->prev; delete timer; return ; } //如果位于链表中间,则将其前后定时器串联起来 timer->prev->next = timer->next; timer->next->prev = timer->prev; delete timer;}void sort_timer_lst::tick() { if (!head) { return ; } printf("timer tick : \n"); time_t cur = time(nullptr); //获得系统当前时间 util_timer* tmp = head; //从头节点开始依次处理每个定时器,直到遇到一个尚未到期的定时器, //这就是定时器的核心逻辑 while (tmp) { //因为每个定时器都使用绝对时间作为超时值,所以我们可以把定时器的超时值和 //系统当前时间,比较以判断定时器是否到期 if (cur < tmp->expire) { break; } //调用定时器的回调函数,以执行定时任务 tmp->cb_func(tmp->user_data); //执行完定时器中的定时任务之后,就将它从链表中删除,并重置链表头节点 head = tmp->next; if (head) { head->prev = nullptr; } delete tmp; tmp = head; }}void sort_timer_lst::add_timer(util_timer* timer, util_timer* lst_head) { util_timer* prev = lst_head; util_timer* tmp = prev->next; //遍历lst_head节点之后的部分链表,直到找到一个超时时间大于目标定时器的超时时间的节点, //病将目标定时器插入该节点之前 while (tmp) { if (timer->expire < tmp->expire) { prev->next = timer; timer->next = tmp; timer->prev = prev; tmp->prev = timer; } prev = tmp; tmp = tmp->next; } //遍历结束仍未找到超时时间大于目标定时器超时时间的节点,插入尾部 if (!tmp) { prev->next = timer; timer->prev = prev; timer->next = nullptr; tail = timer; }}#endif

(2)时间轮的简单实现

//时间轮#ifndef _TIME_WHEEL_TIMER_H#define _TIME_WHEEL_TIMER_H#include 
#include
#include
#define BUFFER_SIZE 64class tw_timer;//绑定socket和定时器struct client_data { sockaddr_in address; int sockfd; char buf[BUFFER_SIZE]; tw_timer* timer;};//定时器类class tw_timer {public: tw_timer(int rot, int ts) : next(nullptr), prev(nullptr), rotation(rot), time_slot(ts) {}public: int rotation; //记录定时器在时间轮转多少圈后生效 int time_slot; //记录定时器属于时间轮上哪个槽(对应的链表,下同) void (*cb_func) (client_data*); //定时器回调函数 client_data* user_data; //客户数据 tw_timer* next; //指向下一个定时器 tw_timer* prev; //指向前一个定时器};//时间轮--本质就是定时器类容器class time_wheel {private: //时间轮上槽的数目 static const int N = 60; //每1s时间轮转动一次,即槽间隔为1s static const int SI = 1; //时间轮的槽,其中每个元素指向一个定时器链表,链表无序 tw_timer* slots[N]; int cur_slot; //时间轮的当前槽public: time_wheel() : cur_slot(0) { for (int i = 0; i < N; i++) { slots[i] = nullptr; } } ~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]; } } } //根据定时值timeout创建一个定时器,并把它插入合适的槽中 tw_timer* add_timer(int timeout); //删除目标定时器timer void del_timer(tw_timer* timer); //SI时间到期后,调用该函数,时间轮向前滚动一个槽的间隔 void tick();};tw_timer* time_wheel::add_timer(int timeout) { if (timeout < 0) { return nullptr; } int ticks = 0; //下面根据待插入定时器的超时值计算他将在时间轮转动多少个滴答后被触发,并将该滴答数存 //储与变量ticks中。如果待插入定时器的超时值小于时间轮槽间隔SI,则将ticks向上折合为1 //否则就将ticks向下折合为timeout/SI if (timeout < SI) { ticks = 1; } else { ticks = timeout / SI; } //计算待插入的定时器在时间轮转动多少圈后触发 转ticks个滴答后触发 一共N个滴答(即数组大小N) int rotation = ticks / N; //计算待插入的定时器应该被插入到哪个槽中 int ts = (cur_slot + (ticks % N)) % N; //创建新的定时器,他在时间轮转动rotation圈后被触发,且位于第ts个槽上 tw_timer* timer = new tw_timer(rotation, ts); //如果第ts个槽中尚无任何定时器,则把新建的定时器插入其中,并将该定时器设置为该槽的头节点 if (!slots[ts]) { printf("add timer, rotation is %d, ts is %d, cur_slot is %d\n", rotation, ts, cur_slot); slots[ts] = timer; } //否则,将定时器插入第ts个槽中 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; //slots[ts]是目标定时器所在槽的头节点。如果目标定时器就是该头节点,则需要重置第ts个槽的头节点 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; } delete timer; }}void time_wheel::tick() { tw_timer* tmp = slots[cur_slot]; //取得时间轮上当前槽的头节点 printf("current slot is %d\n", cur_slot); while (tmp) { printf("tick the timer once\n"); //如果定时器的rotation值大于0,则他在这一轮不起作用 //大于0说明要转最起码一圈 if (tmp->rotation > 0) { tmp->rotation--; tmp = tmp->next; } //否则,说明定时器已经到期,于是执行定时任务,然后删除该定时器 else { tmp->cb_func(tmp->user_data); if (tmp == slots[cur_slot]) { printf("delete header in cur_slot\n"); 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; //更新时间轮的当前槽,以反映时间轮的转动}#endif

转载地址:https://blog.csdn.net/weixin_36888577/article/details/80445024 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Linux下安装Tesseract-OCR和以命令行和c++代码的使用方法
下一篇:智能指针shared_ptr与unique_ptr详解

发表评论

最新留言

初次前来,多多关照!
[***.217.46.12]2024年04月15日 14时18分31秒