Linux网络编程 | 高性能定时器 :时间轮、时间堆
文章目錄
- 時(shí)間輪
- 時(shí)間堆
在上一篇博客中我實(shí)現(xiàn)了一個(gè)基于排序鏈表的定時(shí)器容器,但是其存在一個(gè)缺點(diǎn)——隨著定時(shí)器越來越多,添加定時(shí)器的效率也會(huì)越來越低。
而下面的兩個(gè)高效定時(shí)器——時(shí)間輪、時(shí)間堆,會(huì)完美的解決這個(gè)問題。
時(shí)間輪
為了解決排序鏈表的缺點(diǎn),時(shí)間輪運(yùn)用了哈希的思想,將定時(shí)器散列到不同的鏈表上,這樣每條鏈表上的定時(shí)器數(shù)目就明顯的少于全部存在一條鏈表上的情況,此時(shí)的插入操作效率就不會(huì)收到定時(shí)器數(shù)量的影響
在圖上所示的時(shí)間輪中,指針每次會(huì)指向輪子上的一個(gè)槽,并且會(huì)順時(shí)針移動(dòng),每次移動(dòng)則會(huì)指向下一個(gè)槽。其中每次移動(dòng)的時(shí)間間隔就是心搏時(shí)間si,由于時(shí)間輪有N個(gè)槽,所以轉(zhuǎn)動(dòng)一周的時(shí)間就是N * si。
每一個(gè)槽都指向一個(gè)鏈表,每條鏈表之間設(shè)定的定時(shí)時(shí)間都是si的整數(shù)倍,通過利用這個(gè)關(guān)系來將定時(shí)器映射到不同的槽中。并且由于結(jié)構(gòu)呈換環(huán)狀,即使定時(shí)時(shí)間大于一圈N * si,也只需要讓其在對應(yīng)轉(zhuǎn)動(dòng)的圈數(shù)生效即可。
移動(dòng)的槽數(shù) = 定時(shí)時(shí)間 / 轉(zhuǎn)動(dòng)間隔 插入槽 = (當(dāng)前槽 + (移動(dòng)的槽數(shù) % 槽總數(shù)) % 槽總數(shù));所以對于時(shí)間輪來說,要提高定時(shí)精度就需要使得轉(zhuǎn)動(dòng)間隔足夠小。而要提高效率則要求槽數(shù)盡量的多。
#ifndef __TIMER_WHEEL_H__ #define __TIMER_WHEEL_H__#include<time.h> #include<stdio.h> #include<netinet/in.h>const int MAX_BUFFER_SIZE = 1024; const int SLOT_COUNT = 60; const int SLOT_INERTVAL = 1;class tw_timer;//用戶數(shù)據(jù) struct client_data {sockaddr_in addr;int sock_fd;char buff[MAX_BUFFER_SIZE];tw_timer* timer; };//定時(shí)器類 class tw_timer { public:tw_timer(int rot, int ts): _rotation(rot), _time_slot(ts), _next(nullptr), _prev(nullptr){}int _rotation; //旋轉(zhuǎn)的圈數(shù)int _time_slot; //記錄在哪一個(gè)槽中void (*fun)(client_data*); //處理函數(shù)client_data* _user_data; //用戶參數(shù)tw_timer* _next;tw_timer* _prev; };//定時(shí)器鏈表,帶頭尾雙向鏈表,定時(shí)器以升序排序 class timer_wheel { public:timer_wheel(): cur_slot(0){//初始化每個(gè)槽的頭節(jié)點(diǎn)for(int i = 0; i < SLOT_COUNT; i++){_slots[i] = nullptr;}}~timer_wheel(){for(int i = 0; i < SLOT_COUNT; i++){//刪除每一個(gè)槽中的所有節(jié)點(diǎn)tw_timer* cur = _slots[i];while(cur){tw_timer* next = cur->_next;delete cur;cur = next;}} }//防拷貝timer_wheel(const timer_wheel&) = delete;timer_wheel& operator=(const timer_wheel&) = delete;//根據(jù)超時(shí)時(shí)間新建定時(shí)器并插入時(shí)間輪中tw_timer* add_timer(int time_out){//如果超時(shí)時(shí)間為負(fù)數(shù)則直接返回if(time_out < 0){return nullptr;}int ticks = 0; //移動(dòng)多少個(gè)槽時(shí)觸發(fā)//如果超時(shí)時(shí)間小于一個(gè)時(shí)間間隔,則槽數(shù)取整為1if(time_out < SLOT_INERTVAL){ticks = 1;}else{//計(jì)算移動(dòng)的槽數(shù)ticks = time_out / SLOT_INERTVAL;}int rotation = ticks / SLOT_COUNT; //計(jì)算插入的定時(shí)器移動(dòng)多少圈后會(huì)被觸發(fā)int time_slot = (cur_slot + (ticks % SLOT_COUNT) % SLOT_COUNT); //計(jì)算其應(yīng)該插入的槽位tw_timer* timer = new tw_timer(rotation, time_slot);//如果要插入的槽為空,則成為該槽的頭節(jié)點(diǎn)if(_slots[time_slot] == nullptr){_slots[time_slot] = timer;}//否則頭插進(jìn)入該槽中else{timer->_next = _slots[time_slot];_slots[time_slot]->_prev = timer;_slots[time_slot] = timer;}return timer;}//刪除指定定時(shí)器void del_timer(tw_timer* timer){if(timer == nullptr){return;}int time_slot = timer->_time_slot;//如果該定時(shí)器為槽的頭節(jié)點(diǎn),則讓下一個(gè)節(jié)點(diǎn)成為新的頭節(jié)點(diǎn)if(timer == _slots[time_slot]){_slots[time_slot] = _slots[time_slot]->_next;if(_slots[time_slot]){_slots[time_slot]->_prev = nullptr;}delete timer;timer = nullptr;}//此時(shí)槽為中間節(jié)點(diǎn),正常的鏈表刪除操作即可else{timer->_prev->_next = timer->_next;if(timer->_next){timer->_next->_prev = timer->_prev;}delete timer;timer = nullptr;} }//處理當(dāng)前槽的定時(shí)事件,并使時(shí)間輪轉(zhuǎn)動(dòng)一個(gè)槽void tick(){tw_timer* cur = _slots[cur_slot];while(cur){//如果不在本輪進(jìn)行處理,則輪數(shù)減一后跳過if(cur->_rotation > 0){--cur->_rotation;cur = cur->_next;}//本輪需要處理的定時(shí)器,執(zhí)行定時(shí)任務(wù)后將其刪除else{cur->fun(cur->_user_data);//如果刪除的是頭節(jié)點(diǎn)if(cur == _slots[cur_slot]){_slots[cur_slot] = cur->_next;if(_slots[cur_slot]){_slots[cur_slot]->_prev = nullptr;}delete cur;cur = _slots[cur_slot];}//刪除的是中間節(jié)點(diǎn)else{cur->_prev->_next = cur->_next;if(cur->_next){cur->_next->_prev = cur->_prev;} tw_timer* next = cur->_next;delete cur;cur = next;}}}//本槽處理完成,時(shí)間輪轉(zhuǎn)動(dòng)一個(gè)槽位cur_slot = (cur_slot + 1) % SLOT_COUNT;}private:tw_timer* _slots[SLOT_COUNT]; //時(shí)間輪的槽,每個(gè)槽的元素為一個(gè)無序定時(shí)器鏈表int cur_slot; //當(dāng)前指向的槽 };#endif // !__TIMER_WHEEL_H__ 時(shí)間復(fù)雜度
添加節(jié)點(diǎn):O(1)
刪除節(jié)點(diǎn):O(1)
執(zhí)行定時(shí)任務(wù):O(n)
雖然執(zhí)行定時(shí)任務(wù)的時(shí)間復(fù)雜度為O(n),但是當(dāng)我們使用多個(gè)輪子來實(shí)現(xiàn)時(shí)間輪時(shí),時(shí)間復(fù)雜度會(huì)接近于O(1)
時(shí)間堆
在我們前面討論的定時(shí)器鏈表、時(shí)間輪都是以固定的時(shí)間間隔來調(diào)用到時(shí)檢測函數(shù)tick來檢測是否到期,然后執(zhí)行到期定時(shí)器的回調(diào)函數(shù),這樣的容器存在一個(gè)嚴(yán)重的缺點(diǎn),就是定時(shí)不夠精確。
為了解決這個(gè)缺點(diǎn),在設(shè)計(jì)定時(shí)器容器的時(shí)候可以采用另外一種思路,將所有定時(shí)器中超時(shí)時(shí)間最小的定時(shí)器的超時(shí)時(shí)間設(shè)置為時(shí)間間隔。一旦tick被調(diào)用超時(shí)時(shí)間最小的定時(shí)器必然到期,對其進(jìn)行處理。接著我們再從剩余的定時(shí)器中找出超時(shí)時(shí)間最小的,繼續(xù)以上邏輯。
通過這種方法,就可以實(shí)現(xiàn)準(zhǔn)確的定時(shí)。而這種數(shù)據(jù)結(jié)構(gòu),恰好和我們之前學(xué)過的堆相同,所以我們又將這種以最小堆實(shí)現(xiàn)的定時(shí)器容器稱為時(shí)間堆。
關(guān)于堆的基本操作在這里就不贅述了,如果不了解的可以參考我的往期博客
數(shù)據(jù)結(jié)構(gòu)與算法 | 堆
時(shí)間復(fù)雜度
添加節(jié)點(diǎn):O(logN)
刪除節(jié)點(diǎn):O(logN)
執(zhí)行定時(shí)任務(wù):O(1)
總結(jié)
以上是生活随笔為你收集整理的Linux网络编程 | 高性能定时器 :时间轮、时间堆的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux网络编程 | 定时事件 :Li
- 下一篇: linux 其他常用命令