基于C++11的高精度多级时间轮定时器

0. 定时器简介

定时器通常包括至少两个成员:一个超时时间(通常采用相对时间或者超时时间)和一个超时时间到达后的一个回调函数。

有时候还可能包括回调函数被运行时须要传入的参数,以及是否又一次启动定时器,更改定时器的超时时间等。

假设使用链表作为容器来串联全部的定时器。则每一个定时器还要包括指向下一个定时器的指针成员。进一步,假设链表是双向的,则每一个定时器还须要包括指向前一个定时器的指针成员。

0.1 排序链表的弊端

基于排序链表的定时器使用唯一的一条链表来管理所有的定时器,所以插入操作的效率随着定时器的数目增多而降低。而时间轮使用了哈希表处理冲突的思想,将定时器散列到不同的链表上。这样每条链表上的定时器数目都将明显少于原来的排序链表上的定时器数目,插入操作的效率基本不受定时器数目的影响。

1. 时间轮计时器简介

1.1 简单时间轮简介

简单时间轮

在这个时间轮中,实线指针指向轮子上的一个槽(slot)。它以恒定的速度顺时针转动,每转动一步就指向下一个槽(slot)。每次转动称为一个滴答(tick)。一个tick时间间隔为时间轮的si(slot interval)。该时间轮共有N个槽,因此它转动一周的时间是Nsi.每个槽指向一条定时器链表,每条链表上的定时器具有相同的特征:它们的定时时间相差Nsi的整数倍。时间轮正是利用这个关系将定时器散列到不同的链表中。假如现在指针指向槽cs,我们要添加一个定时时间为ti的定时器,则该定时器将被插入槽ts(timer slot)对应的链表中:

ts=(cs+(ti/si))%N

对于时间轮而言,要提高精度,就要使si的值足够小; 要提高执行效率,则要求N值足够大,使定时器尽可能的分布在不同的槽。

但是简单时间轮存在一个明显的弊端是,在一个轮子的情况下,可跨越的最大计时时长(一个轮子的刻度数总数 * si)和时间轮的精度难以达到平衡,这个时候,多级时间轮即可在不丢失精度的前提下,加大时间轮计时器可计时的跨度。

1.2 多级时间轮简介

时间轮分成多个层级,每一层是一个圈,和时钟类似,和水表更像,如下面图:
水表

当个位的指针转完一圈到达0这个刻度之后,十位的指针转1格;当十位的转完一圈,百位的转1格,以此类推。这样就能表示很长的度数。

时间轮能表达的时间长度,和圈的数量以及每一圈的长度成正比。假设有4圈,每个圈60个刻度,每个刻度表示1毫秒,那么这个时间轮可以表示这么长:

60 x 60 x 60 x 60 = 12,960,000‬(ms) = 3.6 小时

2. 多级时间轮代码实现分析

2.1 时间轮进制的本质分析

考虑到多级时间轮的进位思想,本质是一种计数进制的体现,上面例子中的4个轮子,每个轮子60刻度其实就是60进制。无论采用多少进制,计数的本质都是连续的。所以无论是用60进制,61进制,10进制都是一样的。

2.2 利用位运算的取巧进制实现

对于计算机而言,人类看到的10,20的整数,对它们并不友善。它们更喜欢二进制数,这样它们能够处理的更快。

基于上述前提,这里采用了一个32位的无符号整型作为5级时间轮计时器的计时辅助,此32位无符号整型自程序运行开始一直递增,每一个tick自增1,采用这样的方式计数,可以通过这一个32位无符号整型来同时表示5个轮子的时间刻度位置。

一级轮二级轮三级轮四级轮五级轮
6bit6bit6bit6bit8bit
11111111111111111111111111111111

这么表示的好处是,每个轮子的指针位数可以通过位运算(左右移和位与)来迅速获得,参考这样的宏:

// 第1个轮占的位数
#define TVR_BITS 8
// 第1个轮的长度
#define TVR_SIZE (1 << TVR_BITS)
// 第n个轮占的位数
#define TVN_BITS 6
// 第n个轮的长度
#define TVN_SIZE (1 << TVN_BITS)
// 掩码:取模或整除用
#define TVR_MASK (TVR_SIZE - 1)
#define TVN_MASK (TVN_SIZE - 1)

想取某一个圈的当前指针位置是:

// 第1个圈的当前指针位置
#define FIRST_INDEX(v) ((v) & TVR_MASK)
// 后面第n个圈的当前指针位置
#define NTH_INDEX(v, n) (((v) >> (TVR_BITS + (n - 1) * TVN_BITS)) & TVN_MASK)

2.3 主动轮与从动轮

虽然是多级时间轮,但是每个轮子的作用并不相同

轮子的类型主要有两类:工作轮与从动轮。以本例实现为例子进行讲解,如下图所示:

在这里插入图片描述

进位逻辑

主动轮:即上图中序号最大的5号轮,每一个定时周期指针移动一格。当5轮转动一圈,4轮进一格,4轮转一圈3轮进一格。

从动轮:1-4轮,他们无法自己主动移动,需要被动等待低级轮进位才能移动。

进位逻辑由2.2中分析的位运算天然保证,无需额外加代码实现

执行定时逻辑

主动轮:当刻度指针指向当前槽的时候,槽内的任务被顺序执行。这里采用的STL的std::list,每次新增的任务都插入到任务链的链头。这里选择插入链头和链尾的复杂度其实没本质区别。和C语言版本的list不一样的是,STL中的头插和尾插都是常数复杂度,详见list::emplace_backlist::emplace_front中的Complexity

从动轮:当对应轮的刻度指针指向当前槽的时候,槽内的任务链依次向低级轮(序号较高的轮)转移,特别注意,从动轮没有执行任务权限,只是对任务进行记录与缓存。

这里需要指出的是,定时任务在添加进入时间轮的时候,任务的跳转路径已经可以计算出来。例如插入的时候,处于2号轮的第3个刻度,下一次跳到4号轮的第4个刻度这些信息在插入的那一刻已经可以得到。每个任务插入的时候,立即算好跳转路径,后续转移查表即可,无需重新计算

删除定时器逻辑

若无需涉及删除定时器的功能,那么数据结构可能直接将回调函数对象std::function放在槽中即可,这也是为什么很多开源定时器不提供删除插入定时器中任务的功能,简单好用。

但如果需要能够删除定时器的功能,那么数据结构就需要变化。这里采用了std::shared_ptrstd::unordered_map的方式实现删除功能。

具体实现方式

  1. 使用智能指针(std::shared_ptr)保存定时任务对象的方式。std::shared_ptr对象在时间轮的对应槽中保存一份,同样在std::unordered_map也保存一份。

  2. 每次在主动轮执行任务之前,使用一下std::shared_ptrunique接口判断std::shared_ptr的引用计数,如果是独占的,那么说明这个任务已经被删除,是过期任务。那么不执行直接跳过。

  3. 由于主动轮每次是弹出所有待执行任务,如果任务被跳过不执行,相当于已经被删除。

PS. 由于任务实际执行不在主动轮运行过程中进行,而是由一个异步任务线程池执行,所以这里选择使用std::shared_ptr的好处在于,对于异步任务线程池而言,std::shared_ptr能够确保执行任务的时候,对象是存活的,防止出现意外。

3. 基于ASIO的异步线程池

这里简单的实现了一个基于ASIO的异步线程池,用于接受主动轮槽中到期的任务,是定时器任务的实际执行者。由于需要开发时间轮类的初衷是希望能够监控一系列服务的存活情况,所以这里的异步任务需要获取任务运行结果,这里采用了std::packaged_task的方式,将异步执行任务的结果用std::future传递出来。

设计目的

这样的方式,能够在定时器需要执行的任务无论耗时长短均不会影响到主动轮的嘀嗒(tick)动作,确保每个(tick)的动作与任务执行耗时解耦。

4. time_holder类

这个类很简单,只是为了保持固定的时长,因为即便每次(tick)动作的耗时已经和任务解耦,但是依旧无法保证每次tick的时长是一个常数,所以在每次tick之后,time_holder的时长实际是按照tick之后的时间点与下次tick时间点的一个动态值。这里主要完成这个类的核心接口依旧是标准库中的:std::this_thread::sleep_until

5. 复杂度分析

实现方式StartTimerStopTimerPerTickBookkeeping
基于链表O(1)O(n)O(n)
基于排序链表O(n)O(1)O(1)
基于最小堆O(lgn)O(1)O(1)
基于时间轮O(1)O(1)O(1)

通过复杂度比较,也可以发现时间轮对比其他方式显现的定时器,具有各方面复杂度均为恒定的效果,但万事万物都是均衡的,这样的常数时间复杂度是用较大的空间消耗换来的。可幸的是,这份空间开销目前在承受范围内。

6. 精度分析

与精度相关的代码其实就这么几行。

std::thread th([this](){
    time_holder holder;
    uint32_t times = 1;    
    while (alive) {
        if (times == 0) {  
                // uint32次后再重新定位原点,频繁定位起点会造成累计误差
                // 重定位理论上越少越好,这里不用uint64是因为uint64加法
                // 32位板子上指令较多,如果是64位cpu,可以采用uint64
            holder.reset();
            times = 1;
        }
        tick();
        holder.hold(unit * times++);
    }
});

可以看到主要精度还是由holder.hold保证的,而这又只是一个标准库的简单封装,所以精度依旧是由std::chrono保证的,实测起来准度还是不错的。

参考文献

  1. 知乎:时间轮定时器
  2. 高性能计时器Timer的设计(时间轮和时间堆两种方式)
  3. C++基础-map与unordered_map

附录

time_holder.hpp 源码
 
#pragma once
#include <thread>
#include <chrono>

namespace monitor 
{
	class time_holder
	{
	public:
		time_holder() : t(std::chrono::steady_clock::now()) {}
        // 自类创建开始,需要保持多久时间,如果不足保持时间,则阻塞等待到期,若已经超时,不阻塞直接退出
		void hold(std::chrono::milliseconds msec) {
			std::this_thread::sleep_until(msec + t);
		}
		// 重置time_holder计时时间
		void reset(){
			t = std::chrono::steady_clock::now();
		}
	private:
		std::chrono::steady_clock::time_point t;
	};
}
time_wheel.hpp 源码
 
#pragma once
#include <thread>
#include <chrono>
#include <memory>
#include <array>
#include <list>
#include <functional>
#include <mutex>
#include <algorithm>
#include <unordered_map>

#ifndef USE_BOOST_FUTURE
#include <future>
#else
//#define BOOST_THREAD_PROVIDES_FUTURE
#define BOOST_THREAD_VERSION 4
#include "boost/thread/future.hpp"
#endif

#include "asyncthreadpool.hpp"
#include "time_holder.hpp"

namespace monitor 
{
    class AsyncThreadPool;
    // 第1个轮占的位数
    #define TVR_BITS 8
    // 第1个轮的长度
    #define TVR_SIZE (1 << TVR_BITS)
    // 第n个轮占的位数
    #define TVN_BITS 6
    // 第n个轮的长度
    #define TVN_SIZE (1 << TVN_BITS)
    // 掩码:取模或整除用
    #define TVR_MASK (TVR_SIZE - 1)
    #define TVN_MASK (TVN_SIZE - 1)
    // 第1个圈的当前指针位置
    #define FIRST_INDEX(v) ((v) & TVR_MASK)
    // 后面第n个圈的当前指针位置
    #define NTH_INDEX(v, n) (((v) >> (TVR_BITS + (n - 1) * TVN_BITS)) & TVN_MASK)
    // 最小刻度单位的指数
    #define TIME_UNIT       50

    class TwTimer;

    using MilliSec  = std::chrono::milliseconds;
    using TaskRB    = std::function<bool(void)>;
    using TmPoint   = std::chrono::steady_clock::time_point;
    using ListShptr = std::list<std::shared_ptr<TwTimer>>;
    using DealFunc  = std::function<void(future<bool>&&)>;
    
    const MilliSec  unit(TIME_UNIT);       // 定时器槽之间时间间隔,即最小刻度,单位:毫秒

#ifndef USE_BOOST_FUTURE
	using namespace std;
#else
	using namespace boost;
#endif
    // 任务类,定时任务
    // 只移不可复制类型
    // 复制主要考虑到
    class TwTimer {
    public:
        TwTimer(const MilliSec& msec, TaskRB&& func, bool _loop, 
                const TmPoint& epoch, std::array<uint8_t, 5>&& cur_slots) : ahp(AsyncThreadPool::GetInstance()),
                                            callback(std::move(func)),
                                            t(std::chrono::steady_clock::now() + msec),
                                            timeout(msec),
                                            loop_flag(_loop),
                                            cur_wheel(0),
                                            start_time(epoch)
        {
            init_slot_seq_cur_wheel(std::move(cur_slots));
        }
        // 任务执行函数
        void execute()
        {
            future_bool = ahp.add(callback);
        }
        future<bool>& get() { return future_bool;}
        // 超时标记点往后挪一个timeout
        void do_loop(std::array<uint8_t, 5>&& cur_slots) 
        { 
            init_slot_seq_cur_wheel(std::move(cur_slots)); 
        }
        // 返回此任务是否是循环任务
        bool is_loop()  const {return loop_flag;}
        // 获取此任务的到期时刻
        std::pair<uint8_t, uint8_t> get_wheel_slot() const
        {
            return std::make_pair(cur_wheel, slot_seq[cur_wheel]);
        }
        // 返回tick后需要跳到的轮子序号和对应轮子的槽号
        std::pair<uint8_t, uint8_t> tick() 
        {
            if (cur_wheel == 0)
                return std::make_pair(0, slot_seq[0]);
            uint8_t next_wheel = 0;
            for (int i = cur_wheel - 1; i >= 0; i--)
            {
                if (slot_seq[i] != 0) {
                    next_wheel = i;
                    break;
                } //如果一个if都没进去,说明cur_wheel在最低环,和初值0一致,并且是整数倍,应该是马上要执行
            }      
            cur_wheel = next_wheel;
            return std::make_pair(next_wheel, slot_seq[next_wheel]);
        }
        uint32_t  get_loop_time() const {return timeout.count();}
        // 只移不可复制类型
        TwTimer(const TwTimer& orgin) = delete;
        TwTimer& operator=(const TwTimer & orgin) = delete;
        TwTimer(TwTimer&&) = default;
        TwTimer& operator=(TwTimer&&) = default;
    private:
        void init_slot_seq_cur_wheel(const std::array<uint8_t, 5>& cur_slots)
        {
            TmPoint end_epoch = timeout + std::chrono::steady_clock::now();
            uint32_t gap = std::chrono::duration_cast<MilliSec>(end_epoch - start_time).count() / TIME_UNIT ;
            slot_seq[0] = FIRST_INDEX(gap);
            for (int i = 1; i < 5 ; i++)
                slot_seq[i] = NTH_INDEX(gap, i);
            for (int i = 4; i >= 0; i--)
            {
                if (slot_seq[i] != 0 && cur_slots[i] != slot_seq[i]) {
                    cur_wheel = i;
                    break;
                } //如果一个if都没进去,说明cur_wheel在最低环,和初值0一致,并且是整数倍,应该是马上要执行
            } 
        }
        AsyncThreadPool&        ahp;        //异步线程池句柄
        TaskRB                  callback;   //超时后的处理函数
        TmPoint                 t;          //任务到期应该执行时候的时刻点
        const MilliSec          timeout;    //超时时长
        const bool              loop_flag;  //是否循环任务
        std::array<uint8_t, 5>  slot_seq;   //记录此任务在5个环中的位置值
        uint8_t                 cur_wheel;  //记录当前处于的环数
        const TmPoint&          start_time; //总时间轮开始时间
        future<bool>            future_bool;
    };

    
    class Task_Result
    {
    public:
        explicit Task_Result(std::shared_ptr<TwTimer> _tw) : is_loop(_tw->is_loop())
        {
            if (is_loop)     //如果是循环任务,则保留弱指针,以防循环任务后续被删除的时候,这里还有备份
                w_tw = _tw;
            else             //如果是非循环任务,则保留shared指针,确保任务被删除后结果仍旧可以保留
                s_tw = _tw;
        }

        bool get_result(uint32_t miiliseconds) const
        { 
            auto span = chrono::milliseconds(miiliseconds);
            std::shared_ptr<TwTimer> sp;
            if (is_loop) {
                sp = w_tw.lock();
            } else {
                sp = s_tw;
            }
            if (sp) {
                future<bool>& ret = sp->get();
                if (ret.valid())
                {  
                   try {
                        if (ret.wait_for(span) != future_status::timeout) {
                            return ret.get();
                        }
                   }
                   catch (std::exception&) {
                       // printf("[exception caught]");
                       return false;
                   }
                }
            }
            return false;
        }

        // 禁止复制,赋值,移动,移动赋值构造
        // 由于C++11不支持lambda移动捕获(C++14才支持),为了模拟移动捕获,只能开启拷贝构造函数可用
        // 但此类的意图并不是允许拷贝的
		Task_Result(const Task_Result &) = default;
		Task_Result& operator=(const Task_Result &) = default;
		Task_Result(Task_Result&&) = default;
		Task_Result& operator=(Task_Result&&) = default;
    private:
        std::weak_ptr<TwTimer>      w_tw;
        std::shared_ptr<TwTimer>    s_tw;        
        bool                        is_loop;
    };
    

    template<int SLOT>
    class SingleWheel
    {
    public:
        SingleWheel() : cur_slot(0) {}
        // 有新任务加入时的接口
        void add(uint8_t insert_slot_seq, std::shared_ptr<TwTimer>&& sp_tw)
        {
            if (insert_slot_seq >= SLOT)
                return;
            task_list[insert_slot_seq].emplace_front(std::move(sp_tw));
        }
        // 任务弹出
        // @return first:弹出的任务列表
        //         second:当前轮是否触发下一个轮的tick,即是否进位
        std::pair<ListShptr, bool> tick()
        {
            auto&& list = cascade(cur_slot);
            cur_slot = cur_slot + 1 == SLOT ?  0 : cur_slot + 1;
            return std::make_pair(list, cur_slot == 0);
        }
        std::pair<ListShptr, bool> degrade()
        {
            cur_slot = cur_slot + 1 == SLOT ?  0 : cur_slot + 1;
            auto&& list = cascade(cur_slot);
            return std::make_pair(list, cur_slot == 0);            
        }

        // @return 返回此轮当前slot槽号,从0开始
        uint8_t curslot() const { return cur_slot;}
    private:
        // @param remove_slot_seq 需要弹出任务的槽
        // @return 弹出的槽中任务列表
        ListShptr cascade(uint8_t remove_slot_seq)
        {
            ListShptr tmp;
            if (remove_slot_seq >= SLOT)
                return tmp;
            task_list[remove_slot_seq].swap(tmp);
            return tmp;
        }    
        uint8_t                     cur_slot;
        std::array<ListShptr, SLOT> task_list;
    };

    //实现5级时间轮 范围为0~ (2^8 * 2^6 * 2^6 * 2^6 *2^6)=2^32
    class TimeWheel {
    public:
        static TimeWheel& GetInstance()
        {
            static TimeWheel t;
            return t;
        }

        // 禁止复制,赋值,移动,移动赋值构造
		TimeWheel(const TimeWheel &) = delete;
		TimeWheel& operator=(const TimeWheel &) = delete;
		TimeWheel(TimeWheel&&) = delete;
		TimeWheel& operator=(TimeWheel&&) = delete;

        void destory() {
            alive = false;
            task_set.clear();
        }

        ~TimeWheel() {
            alive = false;
        }
        // 根据定时值创建定时器,并插入槽中
        Task_Result add_loop_timer(const std::string& name, uint32_t milliseconds_timeout, std::function<bool()>&& func)
        {
            return add_timer(name, milliseconds_timeout, std::move(func), true);
        }

        Task_Result add_once_timer(const std::string& name, uint32_t milliseconds_timeout, std::function<bool()>&& func)
        {
            return add_timer(name, milliseconds_timeout, std::move(func), false);
        }
        
        void del_timer(const std::string& name)
        {
            std::lock_guard<std::recursive_mutex>  g(mtx);
            task_set.erase(name);
        }
        
    private: 
        TimeWheel() :   ahp(AsyncThreadPool::GetInstance()), 
                        epoch(std::chrono::steady_clock::now()),
                        alive(true),
                        first_tw(new SingleWheel<TVR_SIZE>()),
                        last_tws()
        {
            std::thread th([this](){
                time_holder holder;
                uint32_t times = 1;    
                while (alive) {
                    if (times == 0) {  
                         // uint32次后再重新定位原点,频繁定位起点会造成累计误差
                         // 重定位理论上越少越好,这里不用uint64是因为uint64加法
                         // 32位板子上指令较多,如果是64位cpu,可以采用uint64
                        holder.reset();
                        times = 1;
                    }
                    tick();
                    holder.hold(unit * times++);
                }
            });
            th.detach();

            for (auto& item : last_tws)
            {
                item = std::unique_ptr<SingleWheel<TVN_SIZE>>(new SingleWheel<TVN_SIZE>());
            }
        }

        Task_Result add_timer(const std::string& name, uint32_t milliseconds_timeout, std::function<bool()>&& func, bool is_loop)
        {
            std::lock_guard<std::recursive_mutex>  g(mtx);
            auto tw = std::make_shared<TwTimer>(MilliSec(milliseconds_timeout), std::move(func), is_loop, epoch, cur_slots());
            if (is_loop)
                task_set.emplace(name, tw);
            Task_Result ret = Task_Result(tw);
            load_timer(std::move(tw));
            return ret;
        }

        // 此接口给循环任务使用,循环任务不必进行重新构建任务,只需要修改重新算slot和wheel即可
        void add_timer(std::shared_ptr<TwTimer>&& tw)
        {
            tw->do_loop(cur_slots());
            // printf( "tw:%d, first_tw->curslot():%d, last_tws[i]->curslot():%d\n", tw->get_loop_time(), first_tw->curslot(), last_tws[0]->curslot());
            load_timer(std::move(tw));
        }
        // 此接口为将任务装在入对应时间轮
        void load_timer(std::shared_ptr<TwTimer>&& p_tw)
        {
            // first: wheel数,从0开始,second,slot数,从0开始
            std::pair<uint8_t, uint8_t>&& wheel_slot = p_tw->get_wheel_slot(); 
            if (wheel_slot.first > 4)   //大于4理论上不可能出现,退出以防下面array越界
                return;
            // printf( "p_tw:%d, wheel_slot.first:%d, wheel_slot.second:%d\n", p_tw->get_loop_time(), wheel_slot.first, wheel_slot.second);
            if (wheel_slot.first == 0)
                first_tw->add(wheel_slot.second, std::move(p_tw));
            else
                last_tws[wheel_slot.first - 1]->add(wheel_slot.second, std::move(p_tw));
        }
        std::array<uint8_t, 5> cur_slots() const
        {
            std::array<uint8_t, 5> cur_slot;
            cur_slot[0] = first_tw->curslot();
            for (int i = 0; i < 4; i++)
            {
                cur_slot[i + 1] = last_tws[i]->curslot();
            }
            return cur_slot;
        }
        void tick()
        {
            std::lock_guard<std::recursive_mutex>  g(mtx);
            auto&& result = first_tw->tick();
            auto& task_list = result.first;
            for (auto& p_tw : task_list)
            {
                // 这里不会出现判断了unique后,引用次数变化的情况
                // 外层的add_timer和del_timer都被阻塞住
                bool is_loop = p_tw->is_loop();
                if (is_loop && p_tw.unique())  
                    continue;
                p_tw->execute();
                if (is_loop)
                    add_timer(std::move(p_tw));
            }
            if (result.second) // 如果低级轮子进位了
            {
                // printf("jinweile!\n");
                for (int i = 0; i < 4; i++)
                {
                    auto&& res = last_tws[i]->degrade();
                    ListShptr& tl = res.first;
                    for (auto & tw : tl)
                    {
                        std::pair<uint8_t, uint8_t> tik = tw->tick();
                        // printf("进位到哪个轮子:%d, 第几个槽:%d", tik.first, tik.second);
                        if (tik.first > 4)   //大于4理论上不可能出现,退出以防下面array越界
                            continue; 
                        if (tik.first == 0)
                            first_tw->add(tik.second, std::move(tw));
                        else
                            last_tws[tik.first - 1]->add(tik.second, std::move(tw));
                    }
                    if (!res.second)
                        break;
                }
            }
        }
        #if 0
        // 将计时器重新分配轮上的位置
        void realloc(ListShptr&& tl)
        {
            for (auto & tw : tl)
            {
                std::pair<uint8_t, uint8_t> tik = tw->tick();
                if (tik.first > 4)   //大于4理论上不可能出现,退出以防下面array越界
                    continue; 
                if (tik.first == 0)
                    first_tw->add(tik.second, std::move(tw));
                else
                    last_tws[tik.first - 1]->add(tik.second, std::move(tw));
            }
        }
        #endif
        AsyncThreadPool&                        ahp;        
        const TmPoint                           epoch;      // 类开始时候的时间标记点
        std::atomic_bool                        alive;      // tick线程结束标志位
        std::unique_ptr<SingleWheel<TVR_SIZE>>  first_tw;   // 第一个轮 
        std::array<std::unique_ptr<SingleWheel<TVN_SIZE>>, 4>    last_tws;   // 后四个轮 
        // TwTimer的二次计数,使用TwTimer之前,判断unique,如果为真,表示这里没有了,要删除。
        // 这里不建议适用map,unordered_map查找效率比map高好几个数量级
        std::unordered_map<std::string, std::shared_ptr<TwTimer>>      task_set;
        std::recursive_mutex                               mtx;
    };
}
asyncthreadpool.hpp 源码
 
/*
 * @Author: Adam Xiao
 * @Date: 2020-12-10 11:34:09
 * @LastEditors: Adam Xiao
 * @LastEditTime: 2020-12-17 19:09:11
 * @FilePath: ./asyncthreadpool.hpp
 * @Description: 时间轮类辅助异步线程池类,由于之前已有的taskqueque不能取消提交的定时任务,将taskqueque拆分,然后重新写
 *               编译时,CXXFLAGS里要加-DASIO_DISABLE_STD_FUTURE -DASIO_DISABLE_STD_EXCEPTION_PTR -DASIO_DISABLE_STD_NESTED_EXCEPTION
 *              
 *              
 */
#pragma once
#include <thread>
#include <chrono>
#include <memory>
#include <array>
#include <list>
#include <functional>

#ifndef USE_BOOST_FUTURE
#include <future>
#else
//#define BOOST_THREAD_PROVIDES_FUTURE
#define BOOST_THREAD_VERSION 4
#include "boost/thread/future.hpp"
#endif

#ifndef ASIO_STANDALONE
#define ASIO_STANDALONE
#define ASIO_NO_DEPRECATED
#endif

#include "asio/io_context.hpp" 	//io_context use
#include "asio/post.hpp"

#include "time_holder.hpp"

namespace monitor 
{
#ifndef USE_BOOST_FUTURE
	using namespace std;
#else
	using namespace boost;
#endif

    // 不可移动不可复制类型,异步线程池
    class AsyncThreadPool
    {
    public:
        //直接添加任务
        future<bool> add(std::function<bool()> f)
        {
            packaged_task<bool(void)> pt(std::move(f));
            future<bool> fu = pt.get_future();
            asio::post(ioctx, std::move(pt));
            return fu;
        }
         // 只能单例模式生成
        static AsyncThreadPool& GetInstance()
        {
            static AsyncThreadPool tq;
            return tq;
        }
        // 禁止复制,赋值,移动,移动赋值构造
        AsyncThreadPool(const AsyncThreadPool &) = delete;
        AsyncThreadPool& operator=(const AsyncThreadPool &) = delete;
		AsyncThreadPool(AsyncThreadPool&&) = delete;
		AsyncThreadPool& operator=(AsyncThreadPool&&) = delete;
    private:
        asio::io_context ioctx;
        asio::executor_work_guard<asio::io_context::executor_type> work;

        AsyncThreadPool() : work(ioctx.get_executor())
        {
            int thread_num = 8;
            std::vector<std::thread> processors;
            for (int i = 0; i < thread_num; i++) {
                processors.emplace_back(std::thread([this]{
                    ioctx.run();
                }));
            }
            for (auto &th : processors) {
                th.detach();
            }
        }
    };
}

调用样例:

/*
 * @Author: Adam Xiao
 * @Date: 2020-12-09 18:23:35
 * @LastEditors: Adam Xiao
 * @LastEditTime: 2020-12-30 11:20:46
 * @FilePath: /manager/res/test_for_time_wheel.cpp
 * @Description:  此例子编译参数在300和500的时候,需要额外加-lboost_chrono
 * @Makefile:arm-at91-linux-gnueabi-g++ -std=c++11 -o test_once test_main.cpp -pthread -DUSE_BOOST_FUTURE -lboost_system -lboost_thread -DASIO_DISABLE_STD_FUTURE -DASIO_DISABLE_STD_EXCEPTION_PTR -DASIO_DISABLE_STD_NESTED_EXCEPTION -I ../depend/ -L ../../libforat91/ -I ../depend/header -I ../ -lboost_chrono && cp test_once ~/nfs/
 */
#include <array>
#include <string>
#include <sys/time.h>

#include <thread>
#include <string>
#include <chrono>

#include "time_wheel.hpp"

#ifndef USE_BOOST_FUTURE
	using namespace std;
#else
	using namespace boost;
#endif

int main(int argc, char const *argv[])
{
	auto & g = hik::TimeWheel::GetInstance();

	auto f1 = [](const std::string& s)->bool{
		 printf("callback:%s\n", s.c_str());
		return true;
	};

	auto f2 = [](const std::string& s)->bool{
		 printf("callback:%s\n", s.c_str());
		return true;
	};

	auto f3 = [](const std::string& s)->bool{
		 printf("callback:%s\n", s.c_str());
		return true;
	};

	auto tr = g.add_once_timer("gggg", 300, std::bind(f1, "ccccc"));
	std::this_thread::sleep_for(std::chrono::seconds(3));
	printf("result:%d\n", tr.get_result(350));
	g.add_loop_timer("cds", 300, std::bind(f2, "czcdg"));
   while(1)
    {
        static int n = 0;
        std::this_thread::sleep_for(std::chrono::seconds(3));
        n++;
        if (n == 1){
            g.del_timer("cds");
            printf("erased!\n");
        }

        if (n == 2) {
            g.add_loop_timer("gggg", 1000, std::bind(f3, "ccccc"));
            printf("added!again!\n");
        }
	}
	return 0;
}
  • 12
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值