基于时间轮的定时器

Posted xcantaloupe

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于时间轮的定时器相关的知识,希望对你有一定的参考价值。

基于时间轮的定时器

原理

技术图片
图片是一个单层时间轮,当指针走到某一格上,就获取那一格上挂的任务将其执行。
当时如果时间跨度大的时候,格子数明显不够,那么就可以做成多级时间轮。
其实就是当低层的时间轮走了一圈,将它高一层的时间轮走一格,并且将挂在高层时间轮上的任务分配下来。

实现

文件#include"TimeWheel.h"

#include<functional>
#include<list>
#include<thread>
#include<mutex>

struct TimeStamp //时间戳
{
    int ms;
    int s;
    int min;
    bool operator==(TimeStamp timeStamp)
    {
        return timeStamp.ms==ms&&timeStamp.s==s&&timeStamp.min==min;
    }
};

struct Event     //挂在时间槽上的事件
{
    std::function<void(void)> call_back;
    TimeStamp tri_time;    //事件触发的时间
    int dur;               //多久触发
};

class TimeWheel  //时间轮
{
public:
    TimeWheel();
    ~TimeWheel();

    void Start();  //启动
    int AddTimer(int space,std::function<void(void)>& call_back); //添加定期器

private:
    void DoLoop();//主循环
    void InsertTimer(Event &event);  //插入事件到时间槽
    int getMS(const TimeStamp &timeStamp);
    void solvingEvents(std::list<Event>l); //解决时间槽上的事件
    void getNextTime(TimeStamp &nowTimeStamp,int dur);  //得到下个时间

private:
    std::list<Event> *callBackList = nullptr;  //当做时间槽上,每一个槽都是一个链表
    std::mutex _mutex;  //互斥量

    TimeStamp timeStamp;  //事件轮的时间

    int slot_ms;     //毫秒的时间槽数量
    int slot_s;      //秒的时间槽数量
    int slot_min;    //分的时间槽数量

    int step;        //最小的间隔时间
};

文件#include"TimeWheel.cpp"

#include"TimeWheel.h"
#include<iostream>

using namespace std;

TimeWheel::TimeWheel()
{
    cout<<"TimeWheel()"<<endl;
    timeStamp.ms = timeStamp.s = timeStamp.min = 0;

    slot_ms=10;
    slot_s=60;
    slot_min=60;

    step = 100;
    callBackList = new list<Event>[slot_ms+slot_s+slot_min];
}

TimeWheel::~TimeWheel()
{
    delete[] callBackList;
}

void TimeWheel::Start()   //开启一个线程
{
    cout<<"Start()"<<endl;
    std::thread myThread([&]{
        this->DoLoop();
    });

    myThread.detach();
}

void TimeWheel::DoLoop()  //主循环
{
    cout<<"start"<<endl;
    while(true)
    {
        this_thread::sleep_for(chrono::milliseconds(step)); //让线程休息step毫秒的时间

        unique_lock<std::mutex> lock(_mutex);

        cout<<"time:"<<timeStamp.min <<" "<<timeStamp.s <<" "<<timeStamp.ms<<endl;
        TimeStamp per = timeStamp;        //per是原来的时间
        getNextTime(timeStamp,step);      //timeStamp向后走一步


        if(per.min!=timeStamp.min)  //分针有进位
        {
            //cout<<"(check min :" << slot_ms+slot_s+timeStamp.min <<")"<<endl;
            list<Event>& l = callBackList[slot_ms+slot_s+timeStamp.min];
            solvingEvents(l);
            l.clear();
        }
        else if(per.s!=timeStamp.s) //秒针有进位
        {
            //cout<<"(check s :" << slot_ms+timeStamp.s <<")"<<endl;
            list<Event>& l = callBackList[slot_ms+timeStamp.s];
            solvingEvents(l);
            l.clear();
        }
        else if(per.ms!=timeStamp.ms) //毫秒有进位
        {
            //cout<<"(check ms :" << timeStamp.ms <<")"<<endl;
            list<Event>& l = callBackList[timeStamp.ms];
            solvingEvents(l);
            l.clear();
        }

        lock.unlock();
    }
}

int TimeWheel::getMS(const TimeStamp &timeStamp)  //得到时间戳timeStamp一共多少ms
{
    return step * timeStamp.ms + timeStamp.s * 1000 + timeStamp.min * 60 * 1000;
}

void TimeWheel::solvingEvents(list<Event>l)
{
    for (auto item = l.begin(); item != l.end(); item++)
    {
        if(timeStamp == item->tri_time)  //触发时间到了
        {
            item->call_back();           //执行函数

            //如果需要发生多次加入下面两行
            getNextTime(item->tri_time,item->dur);
            InsertTimer(*item);
        }
        else
        {
            InsertTimer(*item);
        }
    }
}

void TimeWheel::getNextTime(TimeStamp &nowTimeStamp,int dur)//获得下一个时间
{
    int next_ms = getMS(nowTimeStamp)+dur;

    nowTimeStamp.min = next_ms/1000/60%slot_min;
    nowTimeStamp.s = (next_ms%(1000*60))/1000;
    nowTimeStamp.ms = (next_ms%1000)/step;
}

//添加定时器
int TimeWheel::AddTimer(int space,function<void(void)>& call_back)
{
    if(space<step)return -1;  //发生事件小于最小间隔
    if(space%step!=0)return -1;

    unique_lock<std::mutex> lock(_mutex);

    Event event;
    event.call_back = call_back;
    event.tri_time.ms = timeStamp.ms;
    event.tri_time.s = timeStamp.s;
    event.tri_time.min = timeStamp.min;
    event.dur = space;
    getNextTime(event.tri_time,event.dur);

    //cout<<"add a "<<space<<" clock"<<endl;
    cout<<event.tri_time.min<<":"<<event.tri_time.s<<":"<<event.tri_time.ms<<endl;
    InsertTimer(event);

    lock.unlock();
    return 0;
}

//先时间轮内插入事件
void TimeWheel::InsertTimer(Event &event)
{
    if(event.tri_time.min != timeStamp.min)   //分钟与现在不同的分钟就插入分的槽
        callBackList[slot_ms + slot_s + event.tri_time.min].push_back(event);
    else if(event.tri_time.s != timeStamp.s)
        callBackList[slot_ms + event.tri_time.s].push_back(event);
    else if(event.tri_time.ms != timeStamp.ms)
        callBackList[event.tri_time.ms].push_back(event);
}

测试文件Main.cpp

#include<cstdio>
#include<cstring>
#include"TimeWheel.h"
#include<iostream>

using namespace std;

void fun100()
{
    cout << "------------fun 100--------------" << endl;
}

void fun200()
{
    cout << "------------fun 200--------------" << endl;
}

void fun500()
{
    cout << "------------fun 500--------------" << endl;
}

void fun1500()
{
    cout << "------------fun 1500-------------" << endl;
}

int main()
{
    function<void(void)> f100 = std::bind(&fun100);
    function<void(void)> f200 = std::bind(&fun200);
    function<void(void)> f500 = std::bind(&fun500);
    function<void(void)> f1500 = std::bind(&fun1500);

    TimeWheel timeWheel;
    timeWheel.Start();

    //加入定时器
    timeWheel.AddTimer(100,f100);
    timeWheel.AddTimer(200,f200);
    timeWheel.AddTimer(500,f500);
    timeWheel.AddTimer(1500,f1500);
    while(true){}
    return 0;
}

实现效果

技术图片

以上是关于基于时间轮的定时器的主要内容,如果未能解决你的问题,请参考以下文章

「linux」定时器方案:红黑树最小堆和时间轮的原理

.Net之时间轮算法(终极版)定时任务

[UWP]分享一个基于HSV色轮的调色板应用

Go语言中时间轮的实现

基于L298N驱动的麦克纳姆轮的小车组装与驱动电机介绍

基于L298N驱动的麦克纳姆轮的小车组装与驱动电机介绍