高性能定时器策略之时间轮定时器算法
时间轮定时器是有多个时间槽(slot )组成,每个时间槽代表时间的基本跨度。类似于一个时钟,时钟指针以恒定的速度每往下走一步,代表一个时间跨度。
时间槽的个数是固定的,用SN (Slot Num) 表示,每转动一次可称为一个滴答(tick),所以转动一周也就需要个SN 滴答。每一个滴答需要的槽间间隔为SI (Slot Interval ),则转一周也就共需要(SN * SI )时间单位。
时间轮的每个槽都指向一个定时器链表,每个链表的定时器都有一个相同的特性:相差(SN * SI )的整数倍,也即是一周的整数倍。正是由于时间轮有这样的特性,所以可以根据定时器的触发时间和时间轮的槽数,把定时器hash到对应的链表上。
一个简单的时间轮如下图:
比如我们要插入一个超时时间为TI 的定时器,那么计算器应该插入到槽对应的链表的方式如下:
TS = ( CS + (TI / SI) ) % SN
CS 代表当前的槽位,因为时间轮定时器不停的在走。
时间轮采用的是hash的思想,把各个定时器分散到不同槽的链表中,这个避免了单个链表的过长,提高了效率。
从上面的公式可以看出,对时间轮而言,当SI 越小的时候,定时器的精度就越高;当SN 槽数越多时,效率越高,因为定时器被分配到不同的链表中,链表的长度也就相对短了,提高了遍历效率。
下面实现一个简单的时间轮,代码如下:
#include #include #include #include #include #include #include #include #include #include #define BUFFER_SIZE 64 //缓冲区长度 #define SLOTS_NUM 60 //时间轮上槽的数目 //结构体声明 typedef struct tw_timer tw_timer; typedef struct client_date client_date; /* EPOLL回调函数*/ typedef void (*epoll_ callback_ t)(int, int); struct tw_timer{ int rotation; //记录定时器在时间轮上转多少圈后触发 int time_slot; //记录定时器属于时间轮上的槽位(对应的链表) void (*cb_func)(client_date *); //定时器回调函数 client_date *user_date; //client数据 tw_timer *prev; //指向前一个定时器 tw_timer *next ; //指向下一个定时器 }; //用户数据 struct client_date{ int sockfd; char buf[BUFFER_SIZE]; tw_timer *time; }; //每1秒时间轮转动一次,也即是槽间隔为1秒 static const int SI = 1; //时间轮的槽, 其中每个元素指向一个定时器链表,链表无序 tw_timer *slots[SLOTS_NUM]; //事件的当前槽 int cur_slot = 0; tw_timer * malloc_tw_timer(int rotation, int time_slot) { tw_timer *timer = (tw timer *)malloc(sizeof(tw_timer)); memset(timer, 0, sizeof(tw_timer)); timer->rotation = rotation; timer->time_slot = time_slot; timer->prev = NULL ; timer->next = NULL ; timer->cb_func = NULL ; timer->user_date = NULL; return timer; } void* free_tw_timer(tw_timer *timer) { if(NULL == timer) return NULL; if(NULL != timer->user_date) free(timer->user_date); free(timer); } void time_wheel() { int i = 0; for(i = 0; i < SLOTS_NUM; i++) slots[i] = NULL; } //遍历每个槽,销毁定时器 void del_time_wheel() { int i = 0; for(i = 0; i < SLOTS_NUM; i++) { tw_timer *tmp = slots[i]; while(tmp) { slots[i] = tmp->next; free_tw_timer(tmp); tmp = slots[i]; } } } /*根据定时器timeout创建一 个定时器,并把它插入到一个合适的槽中*/ tw_timer * add_timer(int timeout) { if(timeout < 0) return NULL; int ticks = 0; /*待插入的超时时间小于时间轮的槽间隔SI,则将ticks向 上调整为1, 否则将ticks向下调整为 timeout/SI */ if(timeout < SI) { ticks = 1; } else { ticks = timeout / SI; } //计算待插入的定时器在时间轮转动多少圈后被触发 int rotation = ticks / SLOTS_NUM; //计算待插入的定时器应该被插入到哪个槽中 int ts = (cur_slot + (ticks % SLOTS_NUM)) % SLOTS_NUM; //创建新的定时器,它在时间轮转动rotation圈后被触发 tw_timer *timer = malloc_tw_timer(rotation, ts); /*若第ts个槽为空,则插入定时器,并将该定时器置为该槽的头结点*/ if(!slots[ts]) { printf("add timer, rotation is %d, ts is %d, cur_ slot is %d ", rotation, ts, cur_slot); slots[ts] = timer; } else //否则,将定时器插入其中 { printf("slots[%d] is not null ", ts); timer->next = slots[ts]; slots[ts]->prev = timer; slots[ts] = timer; } return timer; } void 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 = NULL; } } else { timer->prev->next = timer->next; if(timer->next) { timer->next->prev = timer->prev; } } free_tw_timer(timer); } //SI 时间到后,调用该函数, 时间轮向前滚动一个槽的间隔 void tick() { tw_timer *tmp = slots[cur_slot]; //遍历该槽对应链表的所有定时器,查看是否触发 while(tmp) { if(tmp->rotation > 0) { tmp->rotation--; tmp = tmp->next; } else //到期,执行任务 { tmp->cb_func(tmp->user_date); if(tmp == slots[cur_slot]) { printf("delete head in cur_slot, cur_slot %d ", cur_slot); slots[cur_slot] = tmp->next; free_tw_timer(tmp); if(slots[cur_slot]) slots[cur_slot]->prev = NULL; tmp = slots[cur_slot]; } else { tmp->prev->next = tmp->next; if(tmp->next) { tmp->next->prev = tmp->prev; } tw_timer *tmp2 = tmp->next; free_tw_timer(tmp); tmp = tmp2; } } } //更新时间轮的当前槽,以反映时间轮的转数 cur_slot = ++cur_slot % SLOTS_NUM; }
测试如下 int total = 0; //记录1s定时器触发次数 void test (client_date *cd) { printf("total = %d ", total); } void test_add_timer(int timeout, void (*cb_func)(client_date *)) { tw_timer * timer; timer = add_timer(timeout); timer->cb_func = cb_func; } void test_crete_timer() { //创建一一个2秒5秒10秒70秒, 85秒的定时器 test_add_timer(2, test); test_add_timer(5, test); test_add_timer(10, test); test_add_timer(70, test); test_add_timer(85, test); time_t now ; struct tm *tm_now ; time(&now) ; tm_now = localtime(&now) ; printf("test_crete_timer datetime: %d-%d-%d %d:%d:%d ", tm_now->tm_year+1900, tm_now->tm_mon+1, tm_now->tm_mday, tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec); } //保存触发定时器相应的fd和回调,在该回调中进行遍历时间轮 typedef struct epoll_callback_info { int iFd; epoll_callback_t pfEpollCallBack; }epoll_callback_info; //创建一个触发定时器 int createTimer(long lMSec, int epfd, epoll_callback_t pfEpollCallback) { struct itimerspece stTimer; struct epoll_event event; int timerfd; int iSec = (int)(lMSec/1000); int ret = -1; memset(&event, 0, sizeof(event)); event.events = EPOLLIN | EPOLLHUP | EPOLLERR; memset(&stTimer, 0, sizeof(stTimer)); stTimer.it_value.tv_sec = iSec; stTimer.it_value.tv_nsec = (lMSec%1000)*1000000; stTimer.it_interval.tv_sec = iSec; stTimer.it_interval.tv_nsec = (lMSec%1000)*1000000; timerfd = timerfd_create(CLOCK_MONOTONIC, 0); if(-1 != timerfd) { if(0 == timerfd_settime(timerfd, 0, &stTimer, NULL)) { epoll_callback_info *p = malloc(sizeof(epoll_callback_info)); p->iFd = timerfd; p->pfEpollCallback = pfEpollCallback; event.data.ptr = p; ret = epoll_ctl(epfd, EPOLL_CTL_ADD, timerfd, &event); } else { printf("timerfd_settime Failed"); } /*定时器时间设定或添加epoll错误时,释放资源*/ if(0 != ret) { printf("add to epoll failed or set time failed"); close(timerfd); timerfd = -1; } } else { printf("create timerfd failed"); } return timerfd; } void timer_read_timerFd(int timerfd) { uint64_t exp; (void)read(timerfd, &exp, sizeof(uint64_t)); total++; return; } //epoll上定时器回调,每触发一次则调用一次tick() void commomTimerCB(int uiEvent, int iTimeFd) { timer_read_timerFd(iTimeFd); tick(); } int main() { struct epoll_event wait_event[100]; int epfd; int timerfd; epoll_callback_t pfEpllCallback; int i = 0; struct itimerspece stTimer; long lMSec = 1000; //1s定时器时间 int count; epfd = epoll_create(1); if(-1 == epfd) { printf("epoll create error "); return -1; } printf("create epoll fd: %d ", epfd); time_wheel(); test_create_timer(); timerfd = createTimer(lMSec, epfd, (epoll_callback_t)commomTimerCB); if(-1 == timerfd) { printf("failed to create timer"); return -1; } for(;;) { waitfds = epoll_wait(epfd, wait_event, 100, -1); printf("count %d ", count++); for(i = 0; i < waitfds; i++) { epoll_callback_info *t = (epoll_callback_info*)wait_event[i].data.ptr; pfEpllCallback = (epoll_callback_t)(unsigned long)t->pfEpllCallback; pfEpllCallback(wait_event[i].events, t->iFd); } } return 0; }
生活中的辩证法有时候,失望到一定程度后,反而会开出一朵花来,那朵花的名字叫,无所谓。每一个闪闪发光的人,都在背后熬过了一个又一个不为人知的黑夜,那才是真正值得我们拥有和赞叹的地方。人生中有无数的
我们总避免不了失望和失去,要学会自己哄自己工作丢了可以再找,钱没了可以再挣,朋友走了可以再交,记住,你什么都不缺,你缺的是一份重新开始的勇气。Ifyouloseyourjob,youcanfinditagain,ifyou
往事不言愁,余生不悲秋人世浮沉,繁华落尽,前尘往事,终会成为故事。寻寻觅觅,总会到达终点。曾经再让人留恋,也已成过眼云烟,昨天再美好,也无法重头再来。无论昨日是美好还是悲伤,都不再重要了,重要的是把握好
图集绿水青山惹人醉大美梅县迎客来绿水青山生态美,景色如画引客来。图为桥溪古韵景区。王志成摄漫步桥溪古韵沉浸天然氧吧,畅玩水美村体验农家乐趣,亲子出游雁山湖打卡粉色沙滩国庆假期,梅县区优质的生态环境和旅游产品供给,
连山道市场时空论(二)国际市场的考察国际市场考察三个层面孙子曰知己知彼,百战不殆。中国企业对国际市场的研究,对各个洲各个国家各个地区市场的全面考察衡量,可以从经济地理社会三个层面进行。1。经济层面包括经济发展水平人口
美股开盘道指涨近150点中概股多下跌爱奇艺B站跌超7金融界10月10日消息,因地缘政治紧张局势以及市场担心美联储可能继续采取激进的货币政策行动,市场避险情绪持续升温,美股微幅高开,道指涨近150点,默沙东涨2。7,Rivian跌6。
重庆市到2025年建成充电桩超24万个来源盖世汽车向天歌日前,重庆市发布重庆市推进智能网联新能源汽车基础设施建设及服务行动计划(20222025年),提及到2025年全市要建成充电桩超过24万个。重庆表示,到2025年
NFR是否比NFT更适应本土生长NFT这个词,大家应该已经有深刻的认识了,全称为NonFungibleToken,指非同质化通证,实质是区块链网络里具有唯一性特点的可信数字权益凭证,是一种可在区块链上记录和处理多
教女心得,家庭环境塑造人,用努力学习给她做表率,榜样的力量一直以来在怎么教育孩子这个问题上没有完整的体系,纵然是各种网络上的鸡汤,能够形成各自的人还是少之又少,当然有很多鸡汤根本就是专家的纸上谈兵,白白制造了无数家长的焦虑。我个人始终深信
睡觉出汗的孩子,可不仅仅是因为热了,家长要了解小孩子出现睡觉大汗淋漓的情况被称为盗汗,对于五岁以下的孩子来说,这种情况相对多见,因为孩子此刻由于身体生长旺盛,而排汗功能还在完善建立过程当中,故而汗液多。虽然孩子的盗汗,大致被医
家长必看的正面管教手册!5种错误惩罚方式和5种科学惩罚方式孩子犯了错,要不要惩罚?答案是要。只是说教的话,孩子根本不长记性啊!但惩罚不是目的,孩子们需要规则,也需要尊重。作为两个娃的爸爸,萌医生非常理解,很多时候我们并不是真的想要打骂孩子