0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

定时器作用及实现定时器数据结构选取介绍2

jf_78858299 来源:程序员不是码农 作者:程序员不是码农 2023-04-21 15:20 次阅读

Skynet定时器实现方案

skynet中定时器数据结构

采用时间轮方式,hash表+链表实现,

struct timer_node {  //时间节点
 struct timer_node *next;
    uint32_t expire; //到期滴答数
};
struct link_list {  // 链表
  struct timer_node head;
  struct timer_node *tail;
};
struct timer {
 struct link_list near[256];  // 即将到来的定时器
    struct link_list t[4][64]; // 相对较遥远的定时器
    struct spinlock lock;
    uint32_t time;  // 记录当前滴答数
    uint64_t starttime;
    uint64_t current;
    uint64_t current_point;
};

其中time32位无符号整数, 记录时间片对应数组near[256] ,表示即将到来的定时任务, t[4][64],表示较为遥远的定时任务。

定时器执行流程

图片

skynet_time_wheel

t[3] t[2] t[1] t[0] near
26-32位 20-26位 14-20位 8-14位 0-8位
[2^(8+6x3),2^(8+6x4)-1] [2^(8+6x2),2^(8+6x3)-1] [2^(8+6),2^(8+6x2)-1] [2^8,2^(8+6) -1] [0,2^8-1]
  • 首先检查节点的expiretime的高24位是否相等,相等则将该节点添加到expire低8位值对应数组near的元素的链表中,不相等则进行下一步
  • 检查expiretime的高18位是否相等,相等则将该节点添加到expire低第9位到第14位对应的6位二进制值对应数组t[0]的元素的链表中,否则进行下一步
  • 检查expiretime的高12位是否相等,相等则将该节点添加到expire低第15位到第20位对应的6位二进制值对应数组t[1]的元素的链表中,如果不相等则进行下一步
  • 检查expiretime的高6位是否相等,相等则将该节点添加到expire低第21位到第26位对应的6位二进制值对应数组t[2]的元素的链表中,如果不相等则进行下一步
  • 将该节点添加到expire低第27位到第32位对应的6位二进制值对应数组t[3]的元素的链表中

以下为具体实现,仅贴出主要接口,具体细节请参考skynet源代码。

定时器初始化
// skynet_start.c
// skynet 启动入口
void
skynet_start(struct skynet_config * config) {
    ...
    skynet_timer_init();
    ...
}
// skynet_timer.c
void
skynet_timer_init(void) {
    // 创建全局timer结构 TI
    TI  = timer_create_timer();
    uint32_t current = 0;
    systime(&TI->starttime, ¤t);
    TI->current = current;
    TI->current_point = gettime();
}
添加定时器

通过skynet_server.c中的cmd_timeout调用skynet_timeout添加新的定时器

// TI为全局的定时器指针
static struct timer * TI = NULL; 
int skynet_timeout(uint32_t handle, int time, int session) {
    ...
    struct timer_event event;
    event.handle = handle;  // callback
    eveng.session = session;
    // 添加新的定时器节点
    timer_add(TI, &event, sizeof(event), time);
    return session;
}
// 添加新的定时器节点
static void timer_add(struct timer *T, void 8arg, size_t sz, int time) {
    // 给timer_node指针分配空间,还需要分配timer_node + timer_event大小的空间,
    // 之后通过node + 1可获得timer_event数据
    struct timer_node *node = (struct timer_node *)skynet_malloc(sizeof(*node)+sz);
    memcpy(node+1,arg,sz);
    SPIN_LOCK(T);
    node->expire=time+T->time;
    add_node(T, node);
    SPIN_UNLOCK(T);
}

// 添加到定时器链表里,如果定时器的到期滴答数跟当前比较近(<2^8),表示即将触发定时器添加到T->near数组里
// 否则根据差值大小添加到对应的T->T[i]中
static void add_node(struct timer *T, struct timer_node *node) {
    ...
}
驱动方式

skynet启动时,会创建一个线程专门跑定时器,每帧(0.0025s)调用skynet_updatetime()

// skynet_start.c
static void * 
thread_timer(void *p) {
    struct monitor * m = p;
    skynet_initthread(THREAD_TIMER);
    for (;;) {
        skynet_updatetime();  // 调用timer_update
        skynet_socket_updatetime();
        CHECK_ABORT
        wakeup(m,m->count-1);
        usleep(2500);  // 2500微秒 = 0.0025s
        if (SIG) {
            signal_hup();
            SIG = 0;
        }
    }
    ...
}

每个定时器设置一个到期滴答数,与当前系统的滴答数(启动时为0,1滴答1滴答往后跳,1滴答==0.01s ) 比较得到差值interval;

如果interval比较小(0 <= interval <= 2^8-1),表示定时器即将到来,保存在第一部分前2^8个定时器链表中;否则找到属于第二部分对用的层级中。

// skynet_timer.c
void 
skynet_updatetime(void) {
    ...
    uint32_t diff = (uint32_t)(cp - TI->current_point); 
    TI->current_point = cp;
    TI->current += diff;
    // diff单位为0.01s
    for (i = 0; i < diff; i++){
        timer_update(TI);        
    }
}
static void
timer_update(struct timer *T) {
    SPIN_LOCK(T);
    timer_execute(T); // 检查T->near是否位空,有就处理到期定时器
    timer_shift(T);  // 时间片time++,移动高24位的链表
    timer_execute(T);
    SPIN_UNLOCK(T);
}
// 每帧从T->near中触发到期得定时器
static inline void
timer_execute(struct timer *T) {
  ...
}
// 遍历处理定时器链表中所有的定时器
static inline void
dispatch_list(struct timer_node *current) {
    ...
}
// 将高24位对应的4个6位的数组中的各个元素的链表往低位移
static void
timer_shift(struct timer *T) {
    ...
}
// 将level层的idx位置的定时器链表从当前位置删除,并重新add_node
static void move_list(struct timer *T, int level, int idx) {

}

最小堆实现定时器

最小堆实现例子:boost.asio采用二叉树,go采用四叉树, libuv

具体实现略。

总结

本文主要介绍定时器作用,实现定时器数据结构选取,并详细介绍了跳表,红黑树,时间轮实现定时器的思路和方法。

Python人工智能编程分享 Python 相关的技术文章、工具资料视频教程等。专注Python编程开发学习以及人工智能、机器学习、自然语言处理、深度学习、图像识别、语音识别无人驾驶等前沿AI技术学习!

公众号

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    87

    文章

    11219

    浏览量

    208872
  • 定时器
    +关注

    关注

    23

    文章

    3237

    浏览量

    114434
  • 数据结构
    +关注

    关注

    3

    文章

    573

    浏览量

    40087
收藏 人收藏

    评论

    相关推荐

    定时器/计数基础

    15-1.实现定时的方法15-2.定时器/计数结构和工作原理 15-3.
    发表于 03-23 12:17 48次下载

    555定时器

    555定时器555定时器555定时器555定时器555定时器555定时器555
    发表于 11-10 17:25 52次下载

    基于51单片机的定时器2的操作与实现

    基于51单片机的定时器2的操作与实现,51单片机定时器2的使用!
    发表于 02-22 17:53 12次下载

    定时器介绍

    同时用两个定时器控制蜂鸣器发声, 定时器0控制频率,定时器1控制同个 频率持续的时间,间隔2s依次输出 1,10,50100,200400800
    发表于 02-23 15:56 20次下载

    52单片机——定时器2详解

    文章目录前言一、定时器2简述1.定时器2作用2.定时器
    发表于 11-11 12:51 19次下载
    52单片机——<b class='flag-5'>定时器</b><b class='flag-5'>2</b>详解

    STC51定时器定时器中断

    1.定义定时器介绍: 51单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机内部完成。2.作用
    发表于 11-22 14:51 5次下载
    STC51<b class='flag-5'>定时器</b>与<b class='flag-5'>定时器</b>中断

    stm32—定时器配置

    目录定时器组成通用寄存通用寄存简介:通用定时器 TIMx (TIM2-TIM5 )的功能:通用定时器
    发表于 11-22 17:51 11次下载
    stm32—<b class='flag-5'>定时器</b>配置

    STM32基于cubeMX实现定时器点灯

    Cortex M3内核当中的定时器,它并不属于芯片厂商的外设,也就是说使用ARM内核的不同厂商,都拥有基本结构相同的系统定时器。主要目的是给RTOS提供时钟节拍做时间基准。基本定时器
    发表于 11-23 18:21 19次下载
    STM32基于cubeMX<b class='flag-5'>实现</b><b class='flag-5'>定时器</b>点灯

    STM32定时器-基本定时器

    目录定时器分类基本定时器功能框图讲解基本定时器功能时钟源计数时钟计数自动重装载寄存
    发表于 11-23 18:21 31次下载
    STM32<b class='flag-5'>定时器</b>-基本<b class='flag-5'>定时器</b>

    STM32——高级定时器、通用定时器、基本定时器的区别

    STM32——高级定时器、通用定时器、基本定时器的区别
    发表于 11-26 15:21 110次下载
    STM32——高级<b class='flag-5'>定时器</b>、通用<b class='flag-5'>定时器</b>、基本<b class='flag-5'>定时器</b>的区别

    SysTick 定时器

    的SysTick定时器实现延时,可以不占用系统定时器,节约资源。由于SysTick是在CPU核内部实现的,跟MCU外设无关,因此它的代码可以在不同厂家之间移植。本 章 将 使用系统滴
    发表于 12-05 14:51 9次下载
    SysTick <b class='flag-5'>定时器</b>

    定时器作用实现定时器数据结构选取介绍1

    定时器在各种场景都需要用到,比如游戏的Buff实现,Redis中的过期任务,Linux中的定时任务等等。顾名思义,定时器的主要用途是执行定时
    的头像 发表于 04-21 15:20 1144次阅读
    <b class='flag-5'>定时器</b><b class='flag-5'>作用</b>及<b class='flag-5'>实现</b><b class='flag-5'>定时器</b><b class='flag-5'>数据结构</b><b class='flag-5'>选取</b><b class='flag-5'>介绍</b>1

    什么是软件定时器?软件定时器实现原理

    软件定时器是用程序模拟出来的定时器,可以由一个硬件定时器模拟出成千上万个软件定时器,这样程序在需要使用较多定时器的时候就不会受限于硬件资源的
    的头像 发表于 05-23 17:05 2673次阅读

    定时器设计实现

    返回ITimer类型的共享指针。其中ITimer类中定义了start和stop方法,用于启动或停止当前定时器。 TimerManager还有一个内部类TimerMessageQueue用于实现
    的头像 发表于 11-08 16:50 571次阅读

    定时器实现数据结构选择

    在后端的开发中,定时器有很广泛的应用。 比如: 心跳检测 倒计时 游戏开发的技能冷却 redis的键值的有效期等等,都会使用到定时器定时器实现
    的头像 发表于 11-13 14:22 498次阅读
    <b class='flag-5'>定时器</b>的<b class='flag-5'>实现</b><b class='flag-5'>数据结构</b>选择