多级时间轮定时器_时间轮与哈希表定时

多级时间轮定时器_时间轮与哈希表定时时间轮简述顾名思义,时间轮就像一个轮子,在转动的时候外界会指向轮子不同的区域,该区域就可以被使用。因此只要将不同时间的定时器按照一定的方法散列到时间轮的不同槽(即时间轮划分的区域)之中,就可以实现在运转到某个槽时,进行判断该定时器是否已经到达运行时间(需要判断是由于有的定时器并非在这一圈就需要运行,可能需要后面几圈才会运行。从图中也可以看出,每个槽中的定时器是以(双向)链表…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用


时间轮


简述

顾名思义,时间轮就像一个轮子,在转动的时候外界会指向轮子不同的区域,该区域就可以被使用。因此只要将不同时间的定时器按照一定的方法散列到时间轮的不同槽(即时间轮划分的区域)之中,就可以实现在运转到某个槽时,进行判断该定时器是否已经到达运行时间(需要判断是由于有的定时器并非在这一圈就需要运行,可能需要后面几圈才会运行。

time-wheel

从图中也可以看出,每个槽中的定时器是以(双向)链表形式存储的,每次添加的时候直接插入到链表的开始(头插法)。值得注意的是,由于使用头插法,因此在运行到某个槽时,需要遍历一遍链表,已检查是否有到达时间的计时器,有的话就运行,并删除结点。

至于在每转到一个槽时都要检查是否到达运行时间,可以这样理解:时间轮进行散列的方法就是取余运算,假设每个槽的间隔为1s,共有n个槽,当前转到了第cur个槽,那么一个定时在 t s以后运行的定时器就要放在第( cur + t % n ) % n个槽,并在运行t / n圈后到达该槽时才会运行。因此一个槽中的定时器运行的时间是相差i(i >= 0)个周期的。

所以时间轮简单来说就是散列 + 链表,这样与使用升序链表相比,散列可以直接定位要插入的槽所在位置,可以提高添加定时器的效率,由O(N)到了O(1)。

对实现时间轮来说,最主要的还是链表的操作是否熟练,当然也主要是双向链表的添加与删除。


代码分析

  • 记录定时器的时间信息,从而获取在时间轮中槽的位置,以及在多少圈之后被触发。在定时时间不足槽之间切换的时间时,要将t/n记为1,否则记录t/n的整除结果。
    // timeout为定时时间,SI为槽之间切换的时间
    int ticks;
    if( timeout < SI ) {
        ticks = 1;
    } else {
        ticks = timeout / SI;
    }

    int rotation = ticks / N;  // 被触发的圈数
    int ts = ( cur_slot + ticks % N ) % N;  // 被插入的槽
  • 为提高定时器的添加效率,使用头插法,将定时器添加在槽的开始位置。
  • 使用双向链表,需要注意将后面的结点的pre指向前一个结点。
  • 删除链表时要注意结点是否是第一个结点

代码实现(含注释)

#ifndef _TIMEWHEEL_H_
#define _TIMEWHEEL_H_

#include <time.h>
#include <netinet/in.h>

const int BUFFER_SIZE = 64;

class TwTimer;

// 用户数据,绑定socket和定时器
struct client_data {
    sockaddr_in address;
    int sock_fd;
    char buf[BUFFER_SIZE];
    TwTimer* timer;
};

// 定时器类,时间轮采用双向链表
class TwTimer {
public:
    int rotation;  // 定时器转多少圈后生效
    int time_slot;  // 记录定时器属于时间轮的哪个时间槽
    client_data* user_data;  // 客户数据
    TwTimer* next;  // 指向下一个定时器
    TwTimer* pre;  // 指向上一个定时器
public:
    TwTimer( int rot, int ts ) : rotation(rot), time_slot(ts), next(NULL), pre(NULL) {}
    void (*cb_func)( client_data * );  // 回调函数
};

class TimeWheel {
private: 
    static const int N = 60;  // 槽的数目
    static const int SI = 1;  // 定时器槽之间时间间隔
    TwTimer* slot[ N ];  // 时间轮的槽,指向一个定时器链表,链表无序
    int cur_slot;  // 当前槽
public:
    TimeWheel() : cur_slot(0) {
        for( int i = 0; i < N; i++ ) {
            slot[i] = NULL;
        }
    }

    ~TimeWheel() {
        for( int i = 0; i < N; i++ ) {
            TwTimer* tmp;
            while( tmp = slot[i], tmp ) {
                slot[i] = tmp->next;
                delete tmp;
            }
        }
    }

    TwTimer* add_timer( int timeout );  // 根据定时值创建定时器,并插入槽中
    void del_timer( TwTimer* timer );
    void tick();
};

TwTimer* TimeWheel::add_timer( int timeout ) {
    if( timeout < 0 ) {
        return NULL;
    }

    // 记录多少个tick后被触发,不足最小单位SI的记为1,其余为timeout/SI
    int ticks = 0;
    if( timeout < SI ) {
        ticks = 1;
    } else {
        ticks = timeout / SI;
    }

    int rotation = ticks / N;  // 被触发的圈数
    int ts = ( cur_slot + ticks % N ) % N;  // 被插入的槽
    TwTimer* timer = new TwTimer( rotation, ts );

    // 如果链表为空,则放到头,否则插入到第一个位置
    if( !slot[ts] ) {
        slot[ts] = timer;
    } else {
        timer->next = slot[ts];
        slot[ts]->pre = timer;
        slot[ts] = timer;
    }

    return timer;
}


// 删除定时器
void TimeWheel::del_timer( TwTimer* timer ) {
    if( !timer ) {
        return;
    }

    // 注意链表为双向的
    int ts = timer->time_slot;
    if( timer == slot[ts] ) {
        slot[ts] = slot[ts]->next;
        if( slot[ts] ) {
            slot[ts]->pre = NULL;
        }
    } else {
        timer->pre->next = timer->next;
        if( timer->next ) {
            timer->next->pre = timer->pre;
        }
    }
    delete timer;
}

// SI时间到后,条用该函数,时间轮向前滚动一个槽的间隔
void TimeWheel::tick() {
    TwTimer* tmp = slot[cur_slot];
    while( tmp ) {
        if( tmp->rotation > 0 ) {  // 定时时间未到
            tmp->rotation--;
            tmp = tmp->next;
        } else {  // 定时时间已到
            tmp->cb_func( tmp->user_data );
            if( tmp == slot[cur_slot] ) {  // tmp位于链表首
                slot[cur_slot] = tmp->next;
                if( slot[cur_slot] ) {
                    slot[cur_slot]->pre = NULL;
                }
                delete tmp;
                tmp = slot[cur_slot];
            } else {  // tmp位于链表中
                tmp->pre->next = tmp->next;
                if( tmp->next ) {
                    tmp->next->pre = tmp->pre;
                }
                TwTimer* tmp2 = tmp->next;
                delete tmp;
                tmp = tmp2;
            }
        }
    }
    cur_slot = ( cur_slot + 1 ) % N;
}

#endif
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/188005.html原文链接:https://javaforall.net

(0)
上一篇 2026年4月17日 下午9:40
下一篇 2026年4月17日 下午9:46


相关推荐

  • generic host process已停止工作_windows error reporting 1001

    generic host process已停止工作_windows error reporting 1001故障现象:今天在虚拟机里装了win2003系统,每次重启进入系统时都会报错:generichostprocessforwin32services遇到了一个问题需要关闭。解决方法:先从google查了下相关问题,觉得没一个说来符合我的实际情况。于是回头仔细查看日志,怀疑是安装文件太旧引起的。于是更新补丁,当安装完了提示的99个补丁后,再重启进入系统,…

    2022年10月11日
    11
  • c语言全局变量和局部变量的区别的代码_函数的形参都属于全局变量

    c语言全局变量和局部变量的区别的代码_函数的形参都属于全局变量局部变量和全局变量的区别背景:上午看书阅及这两对概念,有很多相似之处,故记之。一.局部变量&amp;全局变量1局部变量:“在函数内定义的变量”,即在一个函数内部定义的变量,只在本函数范围内有效。2全局变量:“在函数外定义的变量”,即从定义变量的位置到本源文件结束都有效。目的:增加函数间数据联系的渠道。由于同一文件中的所有函数都能引用全局变量的值,因此如果在一个函数中改变了全局变…

    2025年8月3日
    4
  • MyBatis Generator 详解

    MyBatis Generator 详解MyBatisGener 中文文档 MyBatisGener 中文文档地址 http mbg cndocs ml 该中文文档由于尽可能和原文内容一致 所以有些地方如果不熟悉 看中文版的文档的也会有一定的障碍 所以本章根据该中文文档以及实际应用 使用通俗的语言来讲解详细的配置 本文中所有节点的链接都是对应的中文文档地址 可以点击查看详细信息 注 本文后面提到

    2026年3月26日
    2
  • oracle触发器实例

    oracle触发器实例最近有一个需求,一个数据库的表在插入和删除的时候另外一个数据库中的表也做相应的操作。我的想法是1.首先两个数据库可以连接,通过dblink可以解决。2.建立插入和删除才能触发的触发器。直接上代码createorreplacetriggersynch_useridafterinsertordeleteoneosoperatorFOREACHROW–每一行触发一次…

    2022年7月27日
    10
  • rownumber实现分页的方法

    rownumber实现分页的方法利用rownumer实现分页的两种常用方式:1)利用betweendeclare@pagesizeintset@pagesize=4declare@pageindexintset@pageindex=3 select *from(s

    2022年5月3日
    79
  • 1073. 树的中心(树形dp)[通俗易懂]

    1073. 树的中心(树形dp)[通俗易懂]给定一棵树,树中包含 n 个结点(编号1~n)和 n−1 条无向边,每条边都有一个权值。请你在树中找到一个点,使得该点到树中其他结点的最远距离最近。输入格式第一行包含整数 n。接下来 n−1 行,每行包含三个整数 ai,bi,ci,表示点 ai 和 bi 之间存在一条权值为 ci 的边。输出格式输出一个整数,表示所求点到树中其他结点的最远距离。数据范围1≤n≤10000,1≤ai,bi≤n,1≤ci≤105 输入样例: 5 2 1 1 3 2 1 4 3 1 5 1

    2022年8月8日
    6

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号