算法 & 数据结构——时间轮定时器

时间轮定时器

优点:可保证每次执行定时器任务都是O(1)复杂度,在定时器任务密集的情况下,性能优势非常明显。
缺点:内存占用较大,当定时器使用不频繁,处理时间跨度很大的时候,效率低下。

C++实现:

//  timer.h
#pragma once

#include <array>
#include <queue>
#include <deque>
#include <tuple>
#include <memory>
#include <string>
#include <chrono>
#include <vector>
#include <utility>
#include <optional>
#include <algorithm>
#include <functional>

template <int Day>
class Timer {
public:
    using NormalTime = std::tuple<std::uint8_t, std::uint8_t, std::uint8_t, std::uint8_t, std::uint16_t>;

    static NormalTime TimeNormal(std::uint64_t time)
    {
        auto d = (std::uint8_t)(time / 86400000); time -= d * 86400000;
        auto h = (std::uint8_t)(time / 3600000); time -= h * 3600000;
        auto m = (std::uint8_t)(time / 60000); time -= m * 60000;
        auto s = (std::uint8_t)(time / 1000); time -= s * 1000;
        auto ms = (std::uint16_t)time;
        return { d, h, m, s, ms };
    }

    static std::uint64_t TimeNormalNeg(const NormalTime & normal)
    {
        return std::get<0>(normal) * 86400000
            + std::get<1>(normal) * 3600000
            + std::get<2>(normal) * 60000
            + std::get<3>(normal) * 1000
            + std::get<4>(normal);
    }

private:
    struct Task {
        Task()
        { }
        Task(const std::size_t _id,
            const std::uint64_t & _time,
            const std::function<void()> & _func) : id(_id), time(_time), func(_func), normal(TimeNormal(_time))
        { }
        bool operator==(const Task & other) const
        {
            return other.id == id;
        }
        bool operator==(std::size_t id) const
        {
            return this->id == id;
        }
        std::function<void()> func;
        std::uint64_t time;
        NormalTime normal;
        std::size_t id;
    };

    struct Slot {
        std::array<std::vector<Task>, Day> day;
        std::array<std::vector<Task>, 24> hour;
        std::array<std::vector<Task>, 60> minute;
        std::array<std::vector<Task>, 60> second;
        std::array<std::vector<Task>, 1000> millis;
    };

public:
    Timer(std::uint64_t time)
        : _slot(new Slot())
        , _startTime(time)
        , _lastTime(0)
    { }

    ~Timer()
    { }

    void Set(std::size_t id, std::uint64_t time, const std::function<void()> & func)
    {
        InsertTo<0>(Task(id, time - _startTime, func));
    }

    void Timer::Del(size_t id)
    {
        RemoveFrom(_slot->day.begin(), _slot->day.end(), id)
            || RemoveFrom(_slot->hour.begin(), _slot->hour.end(), id)
            || RemoveFrom(_slot->minute.begin(), _slot->minute.end(), id)
            || RemoveFrom(_slot->second.begin(), _slot->second.end(), id)
            || RemoveFrom(_slot->millis.begin(), _slot->millis.end(), id);
    }

    void Update(std::uint64_t time)
    {
        if (time > _lastTime + _startTime)
        {
            do {
                auto[fromd1, fromh1, fromm1, froms1, fromms1] = TimeNormal(_lastTime);
                _lastTime += std::min(time - _startTime - _lastTime, 16ULL);
                auto[fromd2, fromh2, fromm2, froms2, fromms2] = TimeNormal(_lastTime);
                Update<0, Day>(_slot->day.begin(), fromd1, fromd2);
                Update<1, 24>(_slot->hour.begin(), fromh1, fromh2);
                Update<2, 60>(_slot->minute.begin(), fromm1, fromm2);
                Update<3, 60>(_slot->second.begin(), froms1, froms2);
                Update<4, 1000>(_slot->millis.begin(), fromms1, fromms2);
            } while (_lastTime + _startTime != time);
        }
    }

private:
    template <class Iter>
    bool RemoveFrom(Iter first, Iter last, size_t id)
    {
        for (; first != last; ++first)
        {
            auto iter = std::find_if(first->begin(), first->end(),
                [id](const Task & task) { return task == id; });
            if (iter != first->end())
            {
                first->erase(iter);
                return true;
            }
        }
        return false;
    }

    template <int I, int N, class Iter>
    void Update(Iter iter, std::uint64_t first, std::uint64_t last)
    {
        if (first != last)
        {
            for (first = (first + 1) % N; first != last; first = (first + 1) % N)
            {
                Update<I>(*std::next(iter, (std::ptrdiff_t)first));
            }
            Update<I>(*std::next(iter, (std::ptrdiff_t)first));
        }
    }

    template <int N>
    void Update(std::vector<Task> & tasks)
    {
        std::for_each(tasks.begin(), tasks.end(),
            std::bind(&Timer::InsertTo<N + 1>, this, std::placeholders::_1));
        tasks.clear();
    }

    template <int N>
    void InsertTo(const Task & task)
    {
        if constexpr (N == 5)
        {
            std::invoke(task.func);
        }
        else if (std::get<N>(task.normal) == 0)
        {
            InsertTo<N + 1>(task);
        }
        else if (std::get<N>(task.normal) != 0)
        {
            if constexpr (N == 0)
            {
                _slot->day.at(std::get<0>(task.normal)).emplace_back(task);
            }
            else if constexpr (N == 1)
            {
                _slot->hour.at(std::get<1>(task.normal)).emplace_back(task);
            }
            else if constexpr (N == 2)
            {
                _slot->minute.at(std::get<2>(task.normal)).emplace_back(task);
            }
            else if constexpr (N == 3)
            {
                _slot->second.at(std::get<3>(task.normal)).emplace_back(task);
            }
            else if constexpr (N == 4)
            {
                _slot->millis.at(std::get<4>(task.normal)).emplace_back(task);
            }
        }
    }

private:
    uint64_t _lastTime;
    uint64_t _startTime;
    std::unique_ptr<Slot> _slot;
};
//  main.cpp
#include "timer/timer.h"
#include <iostream>
#include <thread>

std::int64_t NowTime(std::int64_t time)
{
    return std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::system_clock::now()).time_since_epoch().count() + time;
}

int main()
{
    auto now = NowTime(0);
    Timer<31> timer(now);
    timer.Set(0, NowTime(5000), [now]() { std::cout << NowTime(0) - now << std::endl; });
    timer.Set(0, NowTime(100), [now]() { std::cout << NowTime(0) - now << std::endl; });
    timer.Set(0, NowTime(0), [now]() { std::cout << NowTime(0) - now << std::endl; });
    timer.Set(0, NowTime(33), [now]() { std::cout << NowTime(0) - now << std::endl; });
    timer.Set(0, NowTime(2333), [now]() { std::cout << NowTime(0) - now << std::endl; });
    timer.Set(0, NowTime(2007), [now]() { std::cout << NowTime(0) - now << std::endl; });
    timer.Set(0, NowTime(3600), [now]() { std::cout << NowTime(0) - now << std::endl; });
    timer.Set(0, NowTime(60000), [now]() { std::cout << NowTime(0) - now << std::endl; });
    timer.Set(0, NowTime(61050), [now]() { std::cout << NowTime(0) - now << std::endl; });
    while (true)
    {
        now = NowTime(0);
        timer.Update(now);
    }
    return 0;
}

linux内核使用的是时间轮定时器,因为内核必须考虑定时器任务密集的情况,同时,内核很容易保证时间跨度是固定的。其他场合下,用堆来实现定时器比较合适。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352

推荐阅读更多精彩内容

  • 很长一段时间里,我错误的认识了定时器。无意中,我发现了“时间轮”这个名词,让我对定时器有了新的看法。 我错误的认为...
    落单的毛毛虫阅读 8,891评论 4 9
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,647评论 18 139
  • 读完梅子涵老师的这本书,心中的那份温暖,那份感动,那份心境,让我想起了白居易笔下那首诗:“绿蚁新醅酒,红泥小火炉...
    范菲阅读 536评论 0 4
  • 每每想佯装着以开玩笑的口吻对他说一句:你不爱我了,才发现他是真的不爱我了。。。 这是苏慢慢最近常常挂在嘴边的话...
    樂a7阅读 466评论 0 1
  • 喜欢一个人,始于颜值,限于才华,忠于人品。 喜欢一个人心里是什么滋味,心里五味杂陈,感受万千。 工作生活已...
    皓月照疏桐阅读 1,194评论 19 26