定時器是遊戲服務器必備的一個功能組件,文章介紹如何用數組結構實現一個定時器。ios
每一個定時器主要包含如下信息:數組
typedef struct TimerInfo { int id; long expired; //超時時間戳(ms) bool is_working; //用於判斷該定時器是否有效 bool is_repeat; // 是否爲循環定時器標識 long interval; //循環定時器循環間隔 } TimerInfo;
id是全局惟一,後面可用於取消未生效的定時器。服務器
除了以上信息,還應包含定時器要執行的任務(通常是函數閉包),這裏爲簡化代碼未寫出.數據結構
定時器管理器:閉包
typedef struct TimerMng { int id; vector<shared_ptr<TimerInfo> > timer_array; //已序數組 map<int, shared_ptr<TimerInfo> > timer_map; //id到定時器的映射 用於根據id取消定時器 mutex mtx; struct timeval now; TimerMng() { id = 0; } } TimerMng;
timer_array是已序數組,根據定時器超時時間排序。函數
timer_map是id到定時器的映射,用於取消未生效的定時器。spa
1 #include <iostream> 2 #include <sys/time.h> 3 #include <unistd.h> 4 #include <thread> 5 #include <vector> 6 #include <map> 7 #include <memory> 8 #include <mutex> 9 #include <stdlib.h> 10 #include <algorithm> 11 12 using namespace std; 13 14 #define THREAD_COUNT 4 15 16 typedef struct TimerInfo 17 { 18 int id; 19 long expired; //超時時間戳(ms) 20 bool is_working; //用於判斷該定時器是否有效 21 bool is_repeat; // 是否爲循環定時器標識 22 long interval; //循環定時器循環間隔 23 24 } TimerInfo; 25 26 // 排序函數 27 bool comp(const shared_ptr<TimerInfo> &t1, const shared_ptr<TimerInfo> &t2) 28 { 29 if(t1.get()->expired < t2.get()->expired) 30 return true; 31 else 32 return false; 33 } 34 35 typedef struct TimerMng 36 { 37 int id; 38 vector<shared_ptr<TimerInfo> > timer_array; //已序數組 39 map<int, shared_ptr<TimerInfo> > timer_map; //id到定時器的映射 用於根據id取消定時器 40 41 mutex mtx; 42 43 struct timeval now; 44 45 TimerMng() 46 { 47 id = 0; 48 } 49 50 } TimerMng; 51 52 TimerMng t_mng; 53 54 void add_timer(bool is_repeat, long expired, long interval) 55 { 56 TimerInfo *t_info = new TimerInfo(); 57 t_info->is_repeat = is_repeat; 58 t_info->expired = expired; 59 t_info->interval = interval; 60 t_info->is_working = true; 61 62 shared_ptr<TimerInfo> timer_info(t_info); 63 64 lock_guard<mutex> guard(t_mng.mtx); 65 int now_id = t_mng.id++; 66 t_info->id = now_id; 67 68 // 二分法尋找合適的插入位置 69 vector<shared_ptr<TimerInfo> >::iterator pos = lower_bound(t_mng.timer_array.begin(), t_mng.timer_array.end(), timer_info, comp); 70 71 t_mng.timer_array.insert(pos, timer_info); 72 t_mng.timer_map.insert(pair<int, shared_ptr<TimerInfo> >(now_id, timer_info) ); 73 74 } 75 76 void update_time() 77 { 78 gettimeofday(&t_mng.now, NULL); 79 } 80 81 void execute_timer() 82 { 83 long now_usec = t_mng.now.tv_sec * 1000 + t_mng.now.tv_usec / 1000; 84 85 while(true) 86 { 87 lock_guard<mutex> guard(t_mng.mtx); 88 89 if (t_mng.timer_array.empty()) 90 break; 91 92 vector<shared_ptr<TimerInfo> >::iterator iter = t_mng.timer_array.begin(); 93 94 if (iter->get()->expired > now_usec ) 95 break; 96 97 if (iter->get()->is_working) 98 { 99 // do something here 100 cout << "timer execute succ, now: " << now_usec << " id: " << iter->get()->id << " " << "expired: " << iter->get()->expired << endl; 101 } 102 103 map<int, shared_ptr<TimerInfo> >::iterator map_iter = t_mng.timer_map.find( iter->get()->id); 104 105 // 從map中移除 106 t_mng.timer_map.erase(map_iter); 107 108 // 從數組中移除 109 t_mng.timer_array.erase(t_mng.timer_array.begin()); 110 111 } 112 } 113 114 115 void timer_thread() 116 { 117 while(1) 118 { 119 update_time(); 120 execute_timer(); 121 // 1ms 1次循環 122 usleep(1000); 123 } 124 } 125 126 void worker_thread() 127 { 128 srand((unsigned)time(0)); 129 while(1) 130 { 131 struct timeval now; 132 gettimeofday(&now, NULL); 133 134 int rand_sec = rand() % 5 + 1; 135 int rand_usec = rand() % 900000 + 1; 136 137 long expired = (now.tv_sec + rand_sec) * 1000 + ( rand_usec / 1000 ); 138 add_timer(false, expired, 0); 139 140 sleep(rand() % 2 + 1); 141 } 142 } 143 144 145 int main() 146 { 147 thread t1(timer_thread); 148 149 vector<thread> v_thread; 150 151 for (int i = 0; i < THREAD_COUNT; ++i) 152 { 153 v_thread.push_back(thread(worker_thread)); 154 } 155 156 t1.join(); 157 158 for (int i = 0; i < THREAD_COUNT; ++i) 159 { 160 v_thread[i].join(); 161 } 162 }
幾個關鍵點介紹:線程
1. 定時器線程執行間隔是1ms,因此加入的定時器的最小循環間隔是1ms。code
2. add_timer 和 execute_timer對timer_mng的 timer_array 和 timer_map操做前,必須先加鎖.blog
3. add_timer是使用二分法(lower_bound)去尋找合適的位置插入新的定時器.
各個操做效率:
1. add_timer, 使用二分法尋找 O(log(N)); 數組插入 O(N); 紅黑樹插入 O(log(N)) ==> O(N) + 2 * O(log(N)).
2. execute_timer, 數組刪除 O(N); 紅黑樹刪除 O(log(N)) ==> O(N) + O(log(N)).
3. cancle_timer(未列出), 紅黑樹尋找 O(log(N)) ==> O(log(N)).
add_timer時數組元素的插入會引發後面元素的移動, execute_timer一定會刪除第一個元素,引發後面全部元素的移動,
這兩個是很是耗時的操做,也是數組實現定時器的一大缺點,後面會介紹使用更高效的數據結構去實現.