日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > linux >内容正文

linux

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

發(fā)布時(shí)間:2024/4/11 linux 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux网络编程 | 高性能定时器 :时间轮、时间堆 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章目錄

  • 時(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)與算法 | 堆

#ifndef __TIMER_HEAP_H__ #define __TIMER_HEAP_H__#include<time.h> #include<iostream> #include<netinet/in.h>const int MAX_BUFFER_SIZE = 1024;class heap_timer;//用戶數(shù)據(jù) struct client_data {sockaddr_in addr;int sock_fd;char buff[MAX_BUFFER_SIZE];heap_timer* timer; };//定時(shí)器類 class heap_timer { public:heap_timer(int delay){_expire = time(nullptr) + delay;}time_t _expire; //到期時(shí)間void (*fun)(client_data*); //處理函數(shù)client_data* _user_data; //用戶參數(shù) };//定時(shí)器鏈表,帶頭尾雙向鏈表,定時(shí)器以升序排序 class timer_heap { public:timer_heap(int capacity = 10) throw (std::exception): _capacity(capacity), _size(0){_array = new heap_timer* [_capacity];//空間申請失敗則拋出異常if(_array == nullptr){throw std::exception();}//初始化數(shù)組for(int i = 0; i < _capacity; i++){_array[i] = nullptr;}}//使用定時(shí)器數(shù)組初始化timer_heap(heap_timer** array, int capacity, int size) throw (std::exception): _capacity(capacity), _size(size){_array = new heap_timer* [_capacity];//容量小于大小時(shí)拋出異常if(capacity < size){throw std::exception(); }//空間申請失敗則拋出異常if(_array == nullptr){throw std::exception();}//拷貝數(shù)據(jù)for(int i = 0; i < _size; i++){_array[i] = array[i];}//初始化剩余空間for(int i = _size; i < _capacity; i++){_array[i] = nullptr;}//從尾部開始調(diào)整堆for(int i = (_size - 2) / 2; i >= 0; i--){adjust_down(i);}}~timer_heap(){for(int i = 0; i < _capacity; i++){delete _array[i];_array[i] = nullptr;}delete[] _array;_array = nullptr;}//防拷貝timer_heap(const timer_heap&) = delete;timer_heap& operator=(const timer_heap&) = delete;//將定時(shí)器插入時(shí)間堆中void push(heap_timer* timer) throw ( std::exception ){if(timer == nullptr){return;}//如果容量滿了則擴(kuò)容if(_size == _capacity){reserve(_capacity * 2); //申請兩倍的空間}//直接在尾部插入,然后向上調(diào)整即可_array[_size] = timer;++_size;adjust_up(_size - 1);}//刪除指定定時(shí)器。void del_timer(heap_timer* timer){if(timer == nullptr){return;}//為了保證堆的結(jié)構(gòu)不被破壞,這里并不會(huì)實(shí)際將他刪除,而是將執(zhí)行函數(shù)清空的偽刪除操作。timer->fun = nullptr;}//獲取堆頂元素heap_timer* top() const{if(empty()){return nullptr;}return _array[0];}//出堆void pop() {//交換堆頂堆尾后直接從首部向下調(diào)整即可std::swap(_array[0], _array[_size - 1]);--_size;adjust_down(0);}//判斷時(shí)間堆是否為空bool empty() const { return _size == 0; }void reserve(int capacity) throw (std::exception){//如果新容量沒有之前的大, 則沒必要擴(kuò)容if(capacity <= _capacity){return;}//開辟新空間heap_timer** temp = new heap_timer* [capacity];if(temp == nullptr){throw std::exception();}//拷貝原數(shù)據(jù)for(int i = 0; i < _size; i++){temp[i] = _array[i];}//初始化剩余空間for(int i = _size; i < capacity; i++){temp[i] = nullptr;}delete[] _array; //刪除原空間 _array = temp; //更新新空間_capacity = capacity;}//以堆頂為基準(zhǔn)執(zhí)行定時(shí)事件void tick(){time_t cur_time = time(nullptr);while(empty()){if(_array[0] == nullptr){return;}//如果堆頂沒有超時(shí),則剩下的不可能超時(shí)if(_array[0]->_expire > cur_time){break;}//如果執(zhí)行任務(wù)為空,則說明被偽刪除,直接出堆即可if(_array[0]->fun != nullptr){_array[0]->fun(_array[0]->_user_data); //執(zhí)行定時(shí)任務(wù)}pop(); //處理完定時(shí)任務(wù)后出堆 }}private://向下調(diào)整算法void adjust_down(int root){int parent = root;int child = root * 2 + 1;while(child < parent){//選出子節(jié)點(diǎn)較小的那個(gè)if(child + 1 < _size && _array[child] > _array[child + 1]){++child;}//如果父節(jié)點(diǎn)比子節(jié)點(diǎn)大則進(jìn)行交換,如果不大于則說明此時(shí)處理已完畢if(_array[parent] > _array[child]){std::swap(_array[parent], _array[child]);}else{break;}//繼續(xù)往下更新parent = child;child = parent * 2 + 1;}}//向上調(diào)整算法void adjust_up(int root){int child = root;int parent = (child - 1) / 2;while(child > 0){if(_array[parent] > _array[child]){std::swap(_array[parent], _array[child]);}else{break;}//往上繼續(xù)更新child = parent;parent = (child - 1) / 2;}}heap_timer** _array; //數(shù)組int _capacity; //數(shù)據(jù)容量int _size; //當(dāng)前數(shù)據(jù)個(gè)數(shù) };#endif // !__TIMER_HEAP_H__

時(shí)間復(fù)雜度
添加節(jié)點(diǎn):O(logN)
刪除節(jié)點(diǎn):O(logN)
執(zhí)行定時(shí)任務(wù):O(1)

總結(jié)

以上是生活随笔為你收集整理的Linux网络编程 | 高性能定时器 :时间轮、时间堆的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。