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

时间轮定时器

优点:可保证每次执行定时器任务都是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内核使用的是时间轮定时器,因为内核必须考虑定时器任务密集的情况,同时,内核很容易保证时间跨度是固定的。其他场合下,用堆来实现定时器比较合适。

    原文作者:落单的毛毛虫
    原文地址: https://www.jianshu.com/p/6c0de0cbd26d
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞