时间轮任务定时器

 time_wheel.h

#include <memory>
#include <list>
#include <vector>
#include <mutex>
#include <thread>

typedef struct TimePos{
    int pos_ms;
    int pos_sec;
    int pos_min;
}TimePos_t;

typedef struct Event {
    int id;
    void(*cb)(void);
    void* arg;
    TimePos_t timePos;
    int interval;
    bool is_repeat;
}Event_t;


class TimeWheel {
    typedef std::shared_ptr<TimeWheel> TimeWheelPtr;
    typedef void (*EventCallback_t)(void );
    typedef std::vector<std::list<Event_t>> EventSlotList_t;
public:
    TimeWheel();
    ~TimeWheel();
    
    void initTimeWheel(int steps, int maxMin);
    int createTimingEvent(int interval, EventCallback_t callback, bool is_repeat = false);
    /**
     * @brief 取消定时任务
     * 
     * @param eventId 任务ID
     */
    void removeTimingEvent(int eventId);

    /**
     * @brief 打印现有任务ID
     */
    void printEvents();

    // 停止所有任务
    void stop();

    // 启动定时器
    bool start();

private:
    void loopForInterval();
    int getCurrentMs(TimePos_t timePos);
    int createEventId();
    int processEvent(std::list<Event_t> &eventList);
    void getTriggerTimeFromInterval(int interval, TimePos_t &timePos);
    void insertEventToSlot(int interval, Event_t& event);

    EventSlotList_t m_eventSlotList;
    TimePos_t m_timePos;
    std::unique_ptr<std::thread> m_loopThread = nullptr;

    int m_firstLevelCount;
    int m_secondLevelCount;
    int m_thirdLevelCount; 
    
    int m_steps;
    int m_increaseId;  // not used
    std::mutex m_mutex;
    bool m_isRunning;
};

time_wheel.cpp

#include "time_wheel.h"
#include "color_log.hpp"

#include <iostream>
#include <memory.h>
#include <chrono>
#include <algorithm>
#include <functional>

TimeWheel::TimeWheel() : m_steps(0), m_firstLevelCount(0), m_secondLevelCount(60), m_thirdLevelCount(0),
                         m_increaseId (1), m_isRunning(false){
                            memset(&m_timePos, 0, sizeof(m_timePos));
                         }

TimeWheel::~TimeWheel() {
    m_isRunning = false;
    if (m_loopThread && m_loopThread->joinable()) {
        m_loopThread->join();
    }  
}

void TimeWheel::loopForInterval() {
    log_debug("start TimeWheel loop !!!");
    while(m_isRunning) {
       std::this_thread::sleep_for(std::chrono::milliseconds(m_steps));
        // printf("唤醒\n");
        TimePos pos = {0};
        TimePos m_lastTimePos = m_timePos;
        // 更新当前 TimeWheel 的槽
        getTriggerTimeFromInterval(m_steps, pos);
        m_timePos = pos;
        {
            std::unique_lock<std::mutex> lock(m_mutex);
            // 如果分钟改变,在整点(分钟)处理
            if (pos.pos_min != m_lastTimePos.pos_min)
            {
                // printf("分钟改变\n");
                log_info("分钟改变 m_eventSlotList : [%d]", m_timePos.pos_min + m_firstLevelCount + m_secondLevelCount);
                std::list<Event_t>* eventList = &m_eventSlotList[m_timePos.pos_min + m_firstLevelCount + m_secondLevelCount];
                processEvent(*eventList);
                eventList->clear();
            }
            else if (pos.pos_sec != m_lastTimePos.pos_sec)
            {
                // 在同一分钟内,但秒数改变,现在是在这个整点秒
                // printf("秒改变\n");
                // log_info("秒改变 m_eventSlotList : [%d]", m_timePos.pos_sec + m_firstLevelCount);
                std::list<Event_t>* eventList = &m_eventSlotList[m_timePos.pos_sec + m_firstLevelCount];
                processEvent(*eventList);
                eventList->clear();
            }
            else if (pos.pos_ms != m_lastTimePos.pos_ms)
            {
                // 现在是在这个毫秒
                // printf("毫秒改变\n");
                // log_info("毫秒改变 m_eventSlotList : [%d]", m_timePos.pos_ms );
                std::list<Event_t>* eventList = &m_eventSlotList[m_timePos.pos_ms];
                processEvent(*eventList);
                eventList->clear();
            }
            // printf("循环结束\n");
        }
     
    }

    log_debug("exit TimeWheel loop !!!");
}

// 初始化 TimeWheel 的步长和最大分钟数,这决定了该轮的最大周期
void TimeWheel::initTimeWheel(int steps, int maxMin)
{
    if (1000 % steps != 0){
        log_info("无效的步长");
        return;
    }
    m_steps = steps;
    m_firstLevelCount = 1000/steps;
    m_thirdLevelCount = maxMin;

    log_info("m_firstLevelCount : [%d] m_secondLevelCount : [%d] m_thirdLevelCount : [%d]",
              m_firstLevelCount, m_secondLevelCount, m_thirdLevelCount);

    m_eventSlotList.resize(m_firstLevelCount + m_secondLevelCount + m_thirdLevelCount);
    log_info("m_eventSlotList size : [%ld]", m_eventSlotList.size());

}

bool TimeWheel::start() {
    if(m_isRunning) {
        log_warn("定时器已经再运行了!!!!");
        return true;
    }
    m_isRunning = true;
    m_loopThread = std::make_unique<std::thread>(std::bind(&TimeWheel::loopForInterval, this));
    if (!m_loopThread->joinable()) {
        log_info("创建线程错误:%s", strerror(errno));
        return false;
    }
    return true;
}

void TimeWheel::stop() {
    m_isRunning = false;
    m_eventSlotList.clear();
    if (m_loopThread->joinable()) {
        m_loopThread->join();
    }
}

// 创建定时事件,参数 interval 表示触发间隔,callback 为回调函数
int TimeWheel::createTimingEvent(int interval, EventCallback_t callback, bool is_repeat){
    if(interval < m_steps || interval % m_steps != 0 || interval >= m_steps*m_firstLevelCount*m_secondLevelCount*m_thirdLevelCount){
        log_info("无效的间隔");
        return 0;
    }
    log_info("开始创建事件");
    Event_t event = {0};
    event.interval = interval;
    event.is_repeat = is_repeat;
    event.cb = callback;
    // 设置时间起点
    event.timePos.pos_min = m_timePos.pos_min;
    event.timePos.pos_sec = m_timePos.pos_sec;
    event.timePos.pos_ms = m_timePos.pos_ms;
    event.id = createEventId();
    // 插入到 TimeWheel 的一个槽中
    std::unique_lock<std::mutex> lock(m_mutex);
    insertEventToSlot(interval, event);
    log_info("创建完成");
    return event.id;
}


int TimeWheel::createEventId() {
    return m_increaseId++;  
}


void TimeWheel::getTriggerTimeFromInterval(int interval, TimePos_t &timePos) {
    // 获取当前时间:毫秒
    int curTime = getCurrentMs(m_timePos);
    // printf("间隔 = %d,当前毫秒 = %d\n", interval, curTime);

    // 计算该间隔应该属于哪个槽
    int futureTime = curTime + interval;
    // printf("未来毫秒 = %d\n", futureTime);
    timePos.pos_min =  (futureTime/1000/60)%m_thirdLevelCount;
    timePos.pos_sec =  (futureTime%(1000*60))/1000;
    timePos.pos_ms = (futureTime%1000)/m_steps;

    // printf("下一分钟位置=%d,秒位置=%d,毫秒位置=%d\n", timePos.pos_min, timePos.pos_sec, timePos.pos_ms);
    return;
}

int TimeWheel::getCurrentMs(TimePos_t timePos) {
    return m_steps * timePos.pos_ms + timePos.pos_sec*1000 +  timePos.pos_min*60*1000;
}

int TimeWheel::processEvent(std::list<Event_t> &eventList){
    // printf("eventList.size=%d\n", eventList.size());

    // 处理当前槽的事件
    for(auto event = eventList.begin(); event != eventList.end(); event ++) {
        // 计算当前毫秒
        int currentMs = getCurrentMs(m_timePos);
        // 计算该事件上次处理的时间(毫秒)
        int lastProcessedMs = getCurrentMs(event->timePos);
        // 计算现在和上次时间的距离(毫秒)
        // int distanceMs = (currentMs - lastProcessedMs + (m_secondLevelCount+1)*60*1000)%((m_secondLevelCount+1)*60*1000);
        // 考虑完成一轮时间轮的情况,计算时间距离
        int distanceMs = (currentMs >= lastProcessedMs) ?
                            (currentMs - lastProcessedMs) :
                            (m_steps * m_firstLevelCount * m_secondLevelCount * m_thirdLevelCount - lastProcessedMs + currentMs);


        // 如果 interval 等于 distanceMs,则需要处理该事件
        if (event->interval == distanceMs) {
            // 处理事件
            event->cb(); //这里可以用线程池来工作,(自行在对象里加一个线程池)不然任务阻塞时间轮就不精准
            // 获取当前位置作为该事件的起点
            event->timePos = m_timePos;
            if (event->is_repeat) {
              // 是循环将该事件插入到槽中
              insertEventToSlot(event->interval, *event);
            } else {
              log_info("event->id [%d] exit pool", event->id);
            }
        } else {
            // 当处理整数点时触发这个条件
            log_info("event->interval != distanceMs, ID [%d] , [%d] != [%d],  [%d] ms after do", event->id, event->interval, distanceMs, event->interval - distanceMs);
            // 虽然该事件在这个位置,但它没有到达时机,它将继续根据 distance ms 移动到下一个槽中进行计算。
            insertEventToSlot((event->interval - distanceMs), *event);
        }
    }
    return 0;
}

void TimeWheel::insertEventToSlot(int interval, Event_t& event)
{
    // log_info("插入事件到槽");

    TimePos_t timePos = {0};

    // 计算该事件应该设置到哪个槽中
    getTriggerTimeFromInterval(interval, timePos);
     {
        // printf("timePos.pos_min=%d, m_timePos.pos_min=%d\n", timePos.pos_min, m_timePos.pos_min);
        // printf("timePos.pos_sec=%d, m_timePos.pos_sec=%d\n", timePos.pos_sec, m_timePos.pos_sec);
        // printf("timePos.pos_ms=%d, m_timePos.pos_ms=%d\n", timePos.pos_ms, m_timePos.pos_ms);

        // 如果分钟不等于当前分钟,首先将其插入到其分钟槽中
        if (timePos.pos_min != m_timePos.pos_min)
        {
            // log_info("ID [%d] 插入到 %d 分钟槽", event.id, m_firstLevelCount + m_secondLevelCount + timePos.pos_min);
            m_eventSlotList[m_firstLevelCount + m_secondLevelCount + timePos.pos_min].emplace_back(event);
        }
        // 如果分钟相等,但秒数改变,将插入到该整点秒的槽中
        else if (timePos.pos_sec != m_timePos.pos_sec)
        {
            // log_info("ID [%d] 插入到 %d 秒槽", event.id, m_firstLevelCount + timePos.pos_sec);
            m_eventSlotList[m_firstLevelCount + timePos.pos_sec].emplace_back(event);
        }
        // 如果分钟和秒数相等,意味着该事件不会在整点触发,将其设置到毫秒槽中
        else if (timePos.pos_ms != m_timePos.pos_ms)
        {
            // log_info("ID [%d] 插入到 %d 毫秒槽", event.id, timePos.pos_ms);
            m_eventSlotList[timePos.pos_ms].emplace_back(event);
        }
     }
    return;
}

/**
 * @brief 取消定时任务
 * 
 * @param eventId 任务ID
 */
void TimeWheel::removeTimingEvent(int eventId) {
  for (auto& eventList : m_eventSlotList) { 
      eventList.remove_if([eventId](const Event_t& event) {
            if (event.id == eventId) {
                log_info("remove Event ID: %d", event.id);
                return true;
            }
            return false;
      });
  }
}

/**
 * @brief 打印现有任务ID
 */
void TimeWheel::printEvents() {
    bool events_is_empty = true; 
    for (const auto& eventList : m_eventSlotList) {
        for (const auto& event : eventList) {
            log_info("Event ID: %d", event.id);
            events_is_empty = false;
        }
    }

    if (events_is_empty) {
        log_info("The timer has no events to work on");
    }
}

time_wheel_main.cpp

#include <iostream>
#include <thread>
#include "time_wheel.h"
#include "color_log.hpp"

using namespace std;

void funccc(void) {
    static int count = 1;
    log_warn("exec 20s function [%d]",count++);
}

void funccc1(void) {
    static int count = 1;
    log_error("exec 40s function [%d]",count++);
}

int main()
{
    TimeWheel wheel;
    wheel.initTimeWheel(50, 2); //初始化tick为50ms,最大轮训时间为2分钟
    wheel.start();

    int task_id1 = wheel.createTimingEvent(20 * 1000, funccc, true);
    log_debug("task_id1 : %d", task_id1);
    int task_id2 = wheel.createTimingEvent(40 * 1000, funccc1, true);
    log_debug("task_id1 : %d", task_id2);
    int task_id3 = wheel.createTimingEvent(500, [](){ 
      static int count = 1;
      // log_debug("exec 500ms function [%d]",count++);
    }, true);
    log_debug("task_id1 : %d", task_id3);
    int task_id4 = wheel.createTimingEvent(60 * 1000, [](){ 
      static int count = 1;
      log_fatal("exec 60s function [%d]",count++);
    }, true);
    log_debug("task_id1 : %d", task_id4);
    int task_id5 = wheel.createTimingEvent(90 * 1000, [](){ 
      static int count = 1;
      log_fatal("exec 90s function [%d]",count++);
    }, true);
    log_debug("task_id1 : %d", task_id5);
    int task_id6 = wheel.createTimingEvent(70 * 1000, [](){ 
      static int count = 1;
      log_fatal("exec 70s function [%d]",count++);
    }, true);
    log_debug("task_id1 : %d", task_id6);
    int task_id7 = wheel.createTimingEvent(35 * 1000, [](){ 
      static int count = 1; 
      log_fatal("exec 35s function [%d]",count++);
    }, true);
    log_debug("task_id1 : %d", task_id7);
    // wheel.printEvents();
    // std::this_thread::sleep_for(std::chrono::seconds(1));
    // wheel.removeTimingEvent(task_id3);
    // wheel.printEvents();
    // std::this_thread::sleep_for(std::chrono::seconds(2));
    // wheel.removeTimingEvent(task_id2);
    // wheel.printEvents();
    // std::this_thread::sleep_for(std::chrono::seconds(3));
    // wheel.removeTimingEvent(task_id1);
    // wheel.printEvents();
    // wheel.stop();

    while (1)
    {
       std::this_thread::sleep_for(std::chrono::milliseconds(10*1000));
    }
}

运行结果

 注意:如果需要保证定时器精准,需要自行修改代码,需在调用处理事件时event->cb(); 这里可以用线程池来工作,(自行在对象里加一个线程池)不然任务阻塞时间轮就不精准。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值