In the past, I used to use timerfd when writing programs and timers, but would it be defective? If you need to create a lot of timers, you will create a lot of descriptors, and if you create a lot of timers, it will undoubtedly cause a lot of switching between user state and kernel state. Can you implement a set of timers in user state? This can effectively reduce the switching between user state and kernel state and improve the program performance
Is there a better implementation? I recently saw a better implementation. After two days of reading, I have understood this code. Now I make a summary. This code idea draws lessons from the message queue of the company's technical director in c + +
Overall architecture:
Time manager timer_ The manager manages two slots, high-level slot and low-level slot. Each slot is 10 bits to form the design of high and low slots
There is no doubt that the low-level tank can store 1024 (0 ~ 1023), so we still need to focus on the low-level tank
The base of the high-level slot is still 1024
The high-level slot is a matrix with width and height of 1024, which just proves that the interval of the high-level slot is the 10th power of 2 to the 20th power of 2. Since the width is 1024, the height is 1024
When the calculation time interval is greater than 1024 seconds, it means looking for the high-level slot. When looking for the first high-level slot, if we want to go to the second high-level slot, it means that we have to move 1024 steps before we can jump to the second slot of the high-level slot to continue looking
Let's look at the lookup code
void timer_manager::run_until(int64_t now) { //printf("run_until,now=%ld curr_expired=%ld next_expired=%ld\n",now,m_curr_expired,m_next_expired); skip_to_next_expired(now) ; //run while( m_curr_expired < now ) { ++m_curr_expired ; m_low_pos = ( m_low_pos +1 ) & m_step_slot_mask ; //move timers from high slot to low slots when run a cycle if(m_low_pos == 0) { shift_high_slot() ; } // trigger expired timers base_timer* expired_head = m_low_array + m_low_pos ; while(expired_head != expired_head->m_next) { base_timer* curr = expired_head->m_next ; base_timer::base_timer_remove(curr) ; curr->on_timeout(this) ; } } update_next_expired() ; }
Code parsing:
When
m_low_pos = ( m_low_pos +1 ) & m_step_slot_mask ;
m_ low_ When POS is 0, it means that we jump to the high-level slot. We need to transfer the data on the high-level slot to the low-level slot
void timer_manager::shift_high_slot() { base_timer* head = m_high_array + m_high_pos ; while(head != head->m_next) { base_timer* curr = head->m_next ; base_timer::base_timer_remove(curr) ; assert(curr->m_expired >= m_curr_expired ) ; int low_pos = (curr->m_expired - m_curr_expired + m_low_pos) & m_step_slot_mask ; base_timer* low_head = m_low_array + low_pos ; base_timer::base_timer_insert(low_head->m_prev,curr,low_head) ; } m_high_pos = ( m_high_pos +1 ) & m_step_slot_mask ; }
It will spread evenly on 0 ~ 1024, so we just need to go from small to large and start from 0 of the status slot.
Complete code display
Header file:
/** * timer_manager.h * * Author: lixingyi (lxyfirst@163.com) */ #pragma once #include <stdint.h> #include <stdlib.h> #include <functional> namespace framework { class timer_manager ; /* * @brief basic timer class , doubled linked */ class base_timer { friend class timer_manager ; public: typedef std::function<void(timer_manager*)> callback_type ; public: base_timer() : m_next(NULL),m_prev(NULL),m_expired(0) { } ; ~base_timer() { if(m_prev && m_next) base_timer_remove(this);} ; /* * @brief get timer counter */ int64_t get_expired() { return m_expired ;} ; /* * @brief set timer counter * @param [in] timer counter * @return 0 on sucess */ int set_expired(int64_t expired) { if(m_next || m_prev) return -1 ; m_expired = expired; return -1 ; } ; void set_callback(const callback_type& callback) { m_callback = callback ; } template<typename T> void set_callback(T* obj,void (T::*callback)(timer_manager* manager) ) { m_callback = std::bind(callback,obj,std::placeholders::_1) ; } bool is_running() { return (m_next || m_prev) ; } ; int inerval = 0; protected: /* * @brief callback , called when timer expired */ void on_timeout(timer_manager* manager) { m_callback(manager); } ; private: static void base_timer_insert(base_timer* prev,base_timer* curr,base_timer* next); static void base_timer_remove(base_timer* timer); private: base_timer* m_next ; base_timer* m_prev ; int64_t m_expired ; callback_type m_callback ; } ; class timer_manager { public: public: timer_manager() ; ~timer_manager(); public: /* * @brief initialize , alloc memory * @param [in] time counter begin to run * @param [in] slot bits * @return 0 on success */ int init(int64_t start_time,int slot_bits) ; void fini() ; /* * @brief insert timer * @param [in] timer to be inserted * @return 0 on success */ int add_timer(base_timer* timer) ; /* * @brief remove timer * @param [in] timer */ void del_timer(base_timer* timer) ; /* * @brief call by main loop to run expired timers untill now */ void run_until(int64_t now) ; int64_t get_curr_expired() const { return m_curr_expired ;} ; /* * @brief the latest timer counter to be expired */ int64_t get_next_expired() const { return m_next_expired ;} ; int32_t get_max_timeout() const { return m_max_expired ; } ; private: enum { step_slot_min_bits = 3 , step_slot_max_bits = 20 , } ; private: timer_manager(const timer_manager& ) ; timer_manager& operator=(const timer_manager&) ; void skip_to_next_expired(int64_t now) ; void update_next_expired() ; void shift_high_slot() ; private: base_timer *m_low_array ; base_timer *m_high_array ; int64_t m_curr_expired ; int64_t m_next_expired ; int8_t m_step_slot_bits ; int16_t m_step_slot_size ; int32_t m_step_slot_mask ; int32_t m_max_expired ; int32_t m_low_pos ; int32_t m_high_pos ; }; }
Implementation file:
/* * timer_manager.cpp * * Author: lixingyi (lxyfirst@163.com) */ #include <assert.h> #include "timer_manager.h" namespace framework { void base_timer::base_timer_insert(base_timer* prev,base_timer* curr,base_timer* next) { prev->m_next = curr ; curr->m_prev = prev ; curr->m_next = next ; next->m_prev = curr ; } void base_timer::base_timer_remove(base_timer* timer) { timer->m_prev->m_next = timer->m_next ; timer->m_next->m_prev = timer->m_prev ; timer->m_next = timer->m_prev = NULL ; } timer_manager::timer_manager():m_low_array(NULL),m_high_array(NULL) { } timer_manager::~timer_manager() { fini() ; } int timer_manager::init(int64_t start_time , int slot_bits) { if(slot_bits < step_slot_min_bits || slot_bits > step_slot_max_bits) return -1 ; if(m_low_array != NULL) return -2 ; m_step_slot_bits = slot_bits ; m_step_slot_size = (0x1 << m_step_slot_bits) ; m_step_slot_mask = ~((~0x0) << m_step_slot_bits ) ; m_max_expired = m_step_slot_size << m_step_slot_bits ; m_curr_expired = start_time ; m_next_expired = m_curr_expired + m_step_slot_size ; m_low_pos = m_high_pos = 0 ; base_timer *timer_array = new base_timer[m_step_slot_size*2] ; if(timer_array == NULL) return -3 ; m_low_array = timer_array ; m_high_array = timer_array + m_step_slot_size ; base_timer* head = NULL ; for(int i=0 ; i < m_step_slot_size ; ++i) { head = m_low_array + i ; head->m_next = head->m_prev = head ; head = m_high_array + i ; head->m_next = head->m_prev = head ; } return 0 ; } void timer_manager::fini() { if(m_low_array) { delete[] m_low_array ; m_low_array = m_high_array = NULL ; } } int timer_manager::add_timer(base_timer* timer) { if(m_low_array == NULL || timer == NULL || (timer->m_next!= NULL) ) return -1 ; int interval = timer->m_expired - m_curr_expired ; if ( interval < 1 || interval > m_max_expired ) return -2 ; //calc positon base_timer* head = NULL ; if ( interval >= m_step_slot_size ) { head = m_high_array +( ( ((interval + m_low_pos) >> m_step_slot_bits) -1 + m_high_pos ) & m_step_slot_mask) ; } else { head = m_low_array + ((interval + m_low_pos) & m_step_slot_mask) ; } base_timer::base_timer_insert(head->m_prev,timer,head) ; //update next expired if(m_next_expired > timer->m_expired ) m_next_expired = timer->m_expired ; return 0 ; } void timer_manager::del_timer(base_timer* timer) { if(timer->m_next && timer->m_prev) { base_timer::base_timer_remove(timer) ; } } ; void timer_manager::run_until(int64_t now) { //printf("run_until,now=%ld curr_expired=%ld next_expired=%ld\n",now,m_curr_expired,m_next_expired); skip_to_next_expired(now) ; //run while( m_curr_expired < now ) { ++m_curr_expired ; m_low_pos = ( m_low_pos +1 ) & m_step_slot_mask ; //move timers from high slot to low slots when run a cycle if(m_low_pos == 0) { shift_high_slot() ; } // trigger expired timers base_timer* expired_head = m_low_array + m_low_pos ; while(expired_head != expired_head->m_next) { base_timer* curr = expired_head->m_next ; base_timer::base_timer_remove(curr) ; curr->on_timeout(this) ; } } update_next_expired() ; } void timer_manager::skip_to_next_expired(int64_t now) { //skip empty slots int skip = (now > m_next_expired ? m_next_expired : now) - m_curr_expired -1 ; if(skip > 0) { m_curr_expired += skip ; m_high_pos = ( m_high_pos +((m_low_pos + skip) >> m_step_slot_bits) ) & m_step_slot_mask ; m_low_pos = ( m_low_pos +skip ) & m_step_slot_mask ; } } void timer_manager::update_next_expired() { //update next expired counter by nearist timer , try max step_slot_size times if(m_next_expired <= m_curr_expired ) { int i = m_low_pos ; for( ; i < m_step_slot_size ; ++i ) { base_timer* next_head = m_low_array + i ; if(next_head != next_head->m_next) break ; } m_next_expired = m_curr_expired + i - m_low_pos ; } } void timer_manager::shift_high_slot() { base_timer* head = m_high_array + m_high_pos ; while(head != head->m_next) { base_timer* curr = head->m_next ; base_timer::base_timer_remove(curr) ; assert(curr->m_expired >= m_curr_expired ) ; int low_pos = (curr->m_expired - m_curr_expired + m_low_pos) & m_step_slot_mask ; base_timer* low_head = m_low_array + low_pos ; base_timer::base_timer_insert(low_head->m_prev,curr,low_head) ; } m_high_pos = ( m_high_pos +1 ) & m_step_slot_mask ; } }