简述
定时器是游戏服务器必备的一个功能组件,文章介绍如何用数组结构实现一个定时器。
数据结构
每个定时器主要包含以下信息:
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到定时器的映射,用于取消未生效的定时器。
实现代码:
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。
2. add_timer 和 execute_timer对timer_mng的 timer_array 和 timer_map操作前,必须先加锁.
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必定会删除第一个元素,引起后面所有元素的移动,
这两个是非常耗时的操作,也是数组实现定时器的一大缺点,后面会介绍使用更高效的数据结构去实现.