Realize wonderful timer

Posted by genius on Tue, 25 Jan 2022 15:48:04 +0100

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 ;


}

}

Topics: C++ Back-end