c++ 時間輪定時器實現
前言
之所以寫這篇文章,是在一篇部落格中看到了時間輪定時器這個東西,感覺很是驚豔,https://www.cnblogs.com/zhongwencool/p/timing_wheel.html。在以前寫windows 程式的時候,windows API 自己就實現了SetTimer 這個呼叫,在超時後會觸發OnTimer的回撥,然後通過timer_id 呼叫我們自己事件處理函式,但是在後臺開發中,一般都需要自己實現,這裡根據部落格實現了自己的定時器。
實現
標頭檔案定義TimeWheel.h
/************************************************************************/ /* TimeWheel實現了一個毫秒級別的定時器,最大支援到分鐘級別*/ /************************************************************************/ #pragma once #include<functional> #include<list> #include<thread> #include<mutex> typedef struct TimePos_ { int ms_pos; int s_pos; int min_pos; }TimePos; typedef struct EventInfo_ { int interval; std::function<void(void)> call_back; TimePos time_pos; int timer_id; }EventInfo; class TimeWheel { public: TimeWheel(); ~TimeWheel(); public: /*step 以毫秒為單位,表示定時器最小時間粒度 *max_timer 表示定時器所能接受的分鐘時間間隔 */ int InitTimerWheel(int step,int max_min); int AddTimer(int interval, std::function<void(void)>& call_back); int DeleteTimer(int timer_id); private: int DoLoop(); int GenerateTimerID(); int InsertTimer(int diff_ms,EventInfo& einfo); int GetNextTrigerPos(int interval,TimePos& time_pos); int GetMS(TimePos time_pos); int DealTimeWheeling(std::list<EventInfo> leinfo); private: std::list<EventInfo> *_pCallbackList = nullptr; std::mutex _mutex; TimePos _time_pos; int _lowCount = 0; int _midCount = 0; int _highCount = 0; int _step_ms = 0; int _timer_count = 0; };
原始檔實現TimerWheel.cpp
#include "TimeWheel.h" #include <iostream> #include <windows.h> using namespace std; TimeWheel::TimeWheel() { memset(&_time_pos, 0, sizeof(_time_pos)); } TimeWheel::~TimeWheel() { } int TimeWheel::InitTimerWheel(int step_ms, int max_min) { if (1000 % step_ms != 0) { cout << "step is not property, should be devided by 1000" << endl; return -1; } int msNeedCount = 1000 / step_ms; int sNeedCount = 60; int minNeedCount = max_min; _pCallbackList = new std::list<EventInfo>[msNeedCount + sNeedCount + minNeedCount]; _step_ms = step_ms; _lowCount = msNeedCount; _midCount = sNeedCount; _highCount = minNeedCount; std::thread th([&]{ this->DoLoop(); }); th.detach(); return 0; } int TimeWheel::AddTimer(int interval, std::function<void(void)>& call_back) { if (interval < _step_ms || interval % _step_ms != 0 || interval >= _step_ms * _lowCount * _midCount * _highCount) { cout << "time interval is invalid" << endl; return -1; } std::unique_lock<std::mutex> lock(_mutex); EventInfo einfo = {0}; einfo.interval = interval; einfo.call_back = call_back; einfo.time_pos.ms_pos = _time_pos.ms_pos; einfo.time_pos.s_pos = _time_pos.s_pos; einfo.time_pos.min_pos = _time_pos.min_pos; einfo.timer_id = GenerateTimerID(); InsertTimer(einfo.interval,einfo); _timer_count++; cout << "insert timer success time_id: " << einfo.timer_id << endl; return einfo.timer_id; } int TimeWheel::DeleteTimer(int time_id) { std::unique_lock<std::mutex> lock(_mutex); int i = 0; int nCount = _lowCount + _midCount + _highCount; for (i = 0; i < nCount; i++) { std::list<EventInfo>& leinfo = _pCallbackList[i]; for (auto item = leinfo.begin(); item != leinfo.end();item++) { if (item->timer_id == time_id) { item = leinfo.erase(item); return 0; } } } if (i == nCount) { cout << "timer not found" << endl; return -1; } return 0; } int TimeWheel::DoLoop() { cout << "........starting loop........" << endl; static int nCount = 0; while (true) { this_thread::sleep_for(chrono::milliseconds(_step_ms)); std::unique_lock<std::mutex> lock(_mutex); cout << ".........this is " << ++nCount <<"loop........."<< endl; TimePos pos = {0}; TimePos last_pos = _time_pos; GetNextTrigerPos(_step_ms, pos); _time_pos = pos; if (pos.min_pos != last_pos.min_pos) { list<EventInfo>& leinfo = _pCallbackList[_time_pos.min_pos + _midCount + _lowCount]; DealTimeWheeling(leinfo); leinfo.clear(); } else if (pos.s_pos != last_pos.s_pos) { list<EventInfo>& leinfo = _pCallbackList[_time_pos.s_pos + _lowCount]; DealTimeWheeling(leinfo); leinfo.clear(); } else if (pos.ms_pos != last_pos.ms_pos) { list<EventInfo>& leinfo = _pCallbackList[_time_pos.ms_pos]; DealTimeWheeling(leinfo); leinfo.clear(); } else { cout << "error time not change" << endl; return -1; } lock.unlock(); } return 0; } int TimeWheel::GenerateTimerID() { int x = rand() % 0xffffffff; int cur_time = time(nullptr); return x | cur_time | _timer_count; } int TimeWheel::InsertTimer(int diff_ms,EventInfo &einfo) { TimePos time_pos = {0}; GetNextTrigerPos(diff_ms, time_pos); if (time_pos.min_pos != _time_pos.min_pos) _pCallbackList[_lowCount + _midCount + time_pos.min_pos].push_back(einfo); else if (time_pos.s_pos != _time_pos.s_pos) _pCallbackList[_lowCount + time_pos.s_pos].push_back(einfo); else if (time_pos.ms_pos != _time_pos.ms_pos) _pCallbackList[time_pos.ms_pos].push_back(einfo); return 0; } int TimeWheel::GetNextTrigerPos(int interval, TimePos& time_pos) { int cur_ms = GetMS(_time_pos); int future_ms = cur_ms + interval; time_pos.min_pos = (future_ms / 1000 / 60) % _highCount; time_pos.s_pos = (future_ms % (1000 * 60)) / 1000; time_pos.ms_pos = (future_ms % 1000) / _step_ms; return 0; } int TimeWheel::GetMS(TimePos time_pos) { return _step_ms * time_pos.ms_pos + time_pos.s_pos * 1000 + time_pos.min_pos * 60 * 1000; } int TimeWheel::DealTimeWheeling(std::list<EventInfo> leinfo) { for (auto item = leinfo.begin(); item != leinfo.end(); item++) { int cur_ms = GetMS(_time_pos); int last_ms = GetMS(item->time_pos); int diff_ms = (cur_ms - last_ms + (_highCount + 1) * 60 * 1000) % ((_highCount + 1) * 60 * 1000); if (diff_ms == item->interval) { item->call_back(); item->time_pos = _time_pos; InsertTimer(item->interval, *item); } else { InsertTimer(item->interval - diff_ms, *item); } } return 0; }
這裡實現的是一個毫秒到分鐘級別的三成時間輪定時器。InitTimerWheel 中有兩個引數,第一個表示支援的最小時間粒度單位毫秒,第二個引數是支援的最大分鐘級別。
時鐘原理說明:
1.1. 初始化一個三層時間輪:毫秒刻盤:1000/step_ms 個MSList, 秒刻盤:60個SList, 時刻盤:max_min個MinList;
1.2. MSTick由外界推動,每跳一輪(1000/step_ms格),MSTick復位至0,同時STick跳1格;
1.3. 同理STick每跳一輪(60格),STick復位至0,同時MinTick跳1格;
1.4. 最高層:MinTick跳一輪(max_min格),MinTick復位至0,一個時間輪完整週期完成.
2.事件原理說明:
2.1. 設定時間為TimeOut的事件時,根據TimeOut算出發生此事件時刻的指標位置{TriggerMin,TriggerS,TriggerMS};
2.2. 用{TriggerMin,TriggerS,TriggerMS}與當前指標{NowMin,NowS,NowMS}對比得出事件存放在哪一個指標(Tick);
2.3. 所有層的指標每跳到下一格(Tick01)都會觸發格子的事件列表,處理每一個事件Event01:
2.3.1 根據事件Event01的剩餘TimeOut算出Event01應該存在上一層(跳得更快)層的位置Pos;
2.3.2 把事件更新到新的Pos(更新TimeOut);
2.3.3 重複處理完Tick01裡面所有的事件;
2.3.4 清空Tick01的事件;
2.3.5 最底層(跳最快)層所有的事件遇到指標Tick都會立即執行;
需要指出的是,這裡和我所貼的部落格中的實現是有點不同的,它所敘述的是一個時分秒級別的定時器,但是我們這裡進行了降級,實現的是一個 毫秒,秒,分鐘級別的定時器。因為個人感覺,這種級別的定時器使用的概率會更大一些
測試
time_wheel.cpp
#include <iostream> #include <functional> #include "TimeWheel.h" using namespace std; void fun100() { cout << "func 100" << endl; } void fun200() { cout << "func 200" << endl; } void fun500() { cout << "func 500" << endl; } void fun1500() { cout << "func 1500" << endl; } void main() { std::function<void(void)> f100 = std::bind(&fun100); std::function<void(void)> f200 = std::bind(&fun200); std::function<void(void)> f500 = std::bind(&fun500); std::function<void(void)> f1500 = std::bind(&fun1500); TimeWheel time_wheel; time_wheel.InitTimerWheel(100, 5); int timer1 = time_wheel.AddTimer(100, f100); int timer2 = time_wheel.AddTimer(200, f200); int timer3 = time_wheel.AddTimer(500, f500); //time_wheel.AddTimer(1500, f1500); bool b = true; int nLoop = 0; while (1) { nLoop++; this_thread::sleep_for(chrono::milliseconds(300)); if (b) { time_wheel.AddTimer(1500, f1500); b = false; } if (nLoop == 3) time_wheel.DeleteTimer(timer1); } }