Timer module design
1 Introduction to timer module
Timer module is a common component in the server. This paper takes you to implement a timer module with basic functions
To design a Timer module, it generally includes two parts: one is the Timer object (Timer), and the other is the manager (TimerManager) who manages the Timer object (also known as Timer container);
2 timer object design
A Timer object is the packaging of a Timer;
id, expiration time and scheduled event callback are the basic members of the core;
Interval, timing times, initial_id_, mutex_ It is added to enrich timer functions, but this is also required in general timer modules;
class Timer { public: using TimeEventCallback = std::function<void()>; Timer(int32_t repeatedtimes,int64_t interval,TimeEventCallback& callback) :repeated_times_(repeatedtimes) ,interval_(interval) ,callback_(std::move(callback)) { expired_time_ = time(nullptr) + interval_; id_ = generateId(); } ~Timer() = default; bool IsExpired() const //Judge whether the timer expires { return time(nullptr) >= expired_time_; } //An interface for a set of member variables time_t ExpiredTime() { return expired_time_; } int64_t Id() { return id_; } int32_t RepeatTimes() {return repeated_times_;} void SetExpiredTime(int64_t t) {expired_time_ = t;} //TimerManager::RemoveTimer use void Run(); //Execute callback static int64_t generateId(); //For constructor calls private: int64_t id_; //Timer id, so that the application and TimerManager can easily use the timer time_t expired_time_; //Expiration time int32_t repeated_times_; //Timing times int64_t interval_; //Timer Intervals TimeEventCallback callback_; //Execution callback when a scheduled event occurs static int64_t initial_id_; //For generating timer id, set it as a static data member to want each timer to have a unique id static std::mutex mutex_; //Used to protect initial_id_ };
Timer implementation:
int64_t Timer::initial_id_ = 0; std::mutex Timer::mutex_{}; void Timer::Run() { callback_(); if(repeated_times_ > 0)//When repeated_times_ == 0, the TimerManager::CheckAndHandle function will delete the timer { repeated_times_--; } expired_time_ += interval_; } int64_t Timer::generateId() { std::lock_guard lk(mutex_);//Because there may be multiple threads through the operation initial_id_, So you need to use a mutex initial_id_++; return initial_id_; }
3 timer manager design
OK, now we can create n timers in the main program, but how to manage it? How to add a new timer, manually delete an old timer, and check whether these timers have expired? If you put this part of the code into the main program, it is too complicated, so you abstract it and write a timermanager class;
A TimeManager object manages all timer objects in the thread, so there must be a data structure to organize these timer objects; (here I stand directly on the shoulders of giants and use priority_queue This article introduces the adapter , logical structure is heap)
In addition, the three core operations of TimeManager are to add a new timer, manually delete an old timer, and check and process the timer;
auto cmp = [](Timer* t1,Timer* t2){return t1->ExpiredTime() > t2->ExpiredTime();};//Customize the operator so that priority_ The earlier the timers in the queue become obsolete, the higher the number of timers in the queue class TimerManager { public: using TimeEventCallback = std::function<void()>; TimerManager() = default; ~TimerManager() = default; //Add timer int64_t AddTimer(int32_t repeatedtimes,int64_t interval,TimeEventCallback& callback); int64_t AddTimer(Timer* timer); void RemoveTimer(int64_t id);//Manually delete timer void CheckAndHandle();//Check and handle private: std::priority_queue<Timer*,std::vector<Timer*>,decltype(cmp)> timers_queue_{cmp};//Manage the data structure of all timers std::unordered_map<int64_t,Timer*> timer_mp_; };
If the cmp is not understood, it is recommended to read it Custom operator;
There is also an STD:: unordered in the data member_ map<int64_ t,Timer*> timer_ mp_; Storing the timer object id and the pointer to the timer object; Why should I have him? Because I found priority_ The queue can only access the top elements of the heap, so I used a map to record it, which is convenient to manually delete the timer;
If there is a better way, please point out! 🙏!
TimerManager implementation:
int64_t TimerManager::AddTimer(int32_t repeatedtimes,int64_t interval,TimeEventCallback& callback) { Timer* t = new Timer(repeatedtimes,interval,callback); timers_queue_.push(t); timer_mp_[t->Id()] = t; return t->Id(); } int64_t TimerManager::AddTimer(Timer* timer) { int id = timer->Id(); timer_mp_[id] = timer; timers_queue_.push(timer); return id; } void TimerManager::RemoveTimer(int64_t id) { Timer* temp = timer_mp_[id]; temp->SetExpiredTime(-1);//In this way, the element to be deleted will sink to the top of the heap timers_queue_.pop(); timer_mp_.erase(id); } void TimerManager::CheckAndHandle() { int len = timers_queue_.size(); for(int i=0;i<len;i++) { Timer* delete_timer; delete_timer = timers_queue_.top(); if(delete_timer->IsExpired()) { delete_timer->Run(); if(delete_timer->RepeatTimes() == 0)//Timer object is invalid, delete { timer_mp_.erase(delete_timer->Id()); delete delete_timer; timers_queue_.pop(); } } else return ;//The following timers do not need to be detected, because its timeout must be later than that of the current timer } }
4 test code
- Test whether the timer adding function of TimerManager is normal
- Test whether the timer removal function of TimerManager is normal
- Test whether the timer function of TimerManager is normal
#include "timer_manager.h" #include <iostream> #include <functional> #include <unistd.h> using namespace std; void TimeFunc1() { std::cout<< "Timer1 Timer On!" <<std::endl; } void TimeFunc2() { std::cout<< "Timer2 Time On!" <<std::endl; } int main() { TimerManager time_manager; std::function<void()> f1 = TimeFunc1; time_manager.AddTimer(3,4,f1); std::function<void()> f2 = TimeFunc2; Timer timer2(4,1,f2); int timer_id2 = time_manager.AddTimer(&timer2);//The passed in parameters are timing times, timing interval and execution callback //while(1) for(int i=0;i<8;i++) { time_manager.CheckAndHandle(); std::cout << "doing other things! cost 2s!" << std::endl; sleep(2); if(i == 1) { time_manager.RemoveTimer(timer_id2); std::cout<< "Timer2 has been removed! " <<std::endl; } } return 0; } /* doing other things! cost 2s! Timer2 Time On! Timer2 Time On! doing other things! cost 2s! Timer2 has been removed! Timer1 Timer On! doing other things! cost 2s! doing other things! cost 2s! Timer1 Timer On! doing other things! cost 2s! doing other things! cost 2s! Timer1 Timer On! doing other things! cost 2s! doing other things! cost 2s! */
5 Summary
It can be seen that the logic of the timer module itself is not complex, but the efficiency should be considered, and what data structure should be used to reduce the time complexity of the above three basic operations;
Common data structures:
- Linked list and queue:
- map:
- Time wheel:
- Time heap:
The priority queue (heap) used in this paper: its search and deletion complexity is O(1); The time consumption is mainly in the automatic adjustment of the heap
6 errors in coding
std::priority_queue<Timer,std::vector<Timer>,decltype(cmp)> timers_queue_{cmp};//correct std::priority_queue<Timer,std::vector<Timer>,decltype(cmp)> timers_queue_(cmp);//Error, error when he is a class member
-
How does the small root heap remove the specified element? How to find the specified element?
Add an unordered_ A map storing a timer object id and a pointer to the timer object;
Undefined symbols for architecture arm64:
"Timer::mutex_", referenced from:
Timer::generateId() in timer_manager.cc.o
ld: symbol(s) not found for architecture arm64
The error code sets the access permission of the function to private
Undefined symbols for architecture arm64:
"Timer::mutex_", referenced from:
Timer::generateId() in timer.cc.o
ld: symbol(s) not found for architecture arm64
Static data members in a class need to be declared inside the class and initialized outside the class, but I did not mutex the static data member_ initialization
Todo: time complexity and performance comparison of each data structure;
reference material:
- Zhang Yuanlong - essence of C + + server development