首页 > 其他分享 >用时间轮实现定时器

用时间轮实现定时器

时间:2024-09-07 19:49:52浏览次数:6  
标签:定时器 实现 TimerNode time list current 时间 TIME

前言

        我们在网络编程中会遇到这样一个场景:我们需要维护大量的TCP连接以进行网络通信,但这些连接中有大量的空闲。我们如何高效地管理这些连接?答案是定时器,我们给每个连接设置定时器来自动管理,规定时间内未进行通信则自动关闭连接。

        但是你应该能很快发现问题,连接数量一多,这些与连接挂钩的定时器也多了,系统的整体性能就会急剧下降,这时候就要请出今天的主角时间轮了。

原理

       时间轮(Timing Wheel)是一种高效的定时器数据结构,广泛用于网络编程、事件驱动编程和操作系统中,用于管理和调度在特定时间点需要执行的任务。它的核心思想是使用一个循环数组(或称为轮子)来存储定时事件,并通过一个指针(或称为“时钟指针”)来跟踪当前时间。

        我们先从时钟入手,我们知道时钟由时针、分针、秒针三部分组成,秒针一圈分针一格,分针一圈时针一格。现在我们将这三个针分开,每个针转一圈都会回到原点。

        我们将其抽象简化成三个数组,把任务按照执行序依次以链表的方式挂在各个槽位,比如有一个36秒的任务,我们就可以计算出它会挂在秒针的第36个槽位上,当秒针指向这个槽位时就会执行这个槽位的任务。这时候你可能疑惑,如果是挂在分针上的任务,那执行逻辑又该如何?这时候就要小心了,当分针走到指定槽位,就需要将槽位上挂载的所有任务重新映射到秒针上,时针也是如此,这样一来,真正的执行者只有秒针数组。

实现思路

       接下来我们一个使用时间轮的定时器。首先来看任务节点,由于我们需要对分针,时针的槽位上的任务重新分配,自然过期时间是必不可少的,回调函数、参数也必不可少,此外,重新分配的机制使节点位置一直在改变致使我们无法删除节点,所以我们添加一个执行字段,等到这个任务启动时通过这个字段判断是否执行此任务。

// 定义定时器节点
class TimerNode {
public:
    std::function<void(TimerNode*)> callback; // 定时器回调函数
    uint32_t expire;                           // 定时器过期时间
    bool cancel;                               // 是否取消定时器
    int id;                                    // 携带参数
};

        节点问题讨论完了,重头戏来了,我们怎么设计时间轮类。前文我们分析了时间轮的由来,既然执行任务都在秒针那一层,分针与时针都需要到点重新分配,我们何不干脆使用两个轮?一个用来执行,一个用于存储。也就是说,我们可以设计一个近程定时器和一个远程定时器。我们将远程定时器分层级来模拟分针和时针,不局限于2个。此外,我们还需要几个成员变量存放当前时间、当前节点,以及一个互斥锁供多线程访问。

        我们还需要几个成员函数:添加任务节点、删除节点(设置执行字段为false)、处理到期的定时任务以及循环推进函数等等。总体设计及解释如下:

#include <stdint.h>
#include<functional>
#include<list>
#include<mutex>

#define TIME_NEAR_SHIFT 8
#define TIME_NEAR (1 << TIME_NEAR_SHIFT)//近程任务
#define TIME_LEVEL_SHIFT 6
#define TIME_LEVEL (1 << TIME_LEVEL_SHIFT)//远程任务
#define TIME_NEAR_MASK (TIME_NEAR-1)
#define TIME_LEVEL_MASK (TIME_LEVEL-1)

struct TimerNode{
    TimerNode*next;
    uint32_t expire;//过期时间
    std::function<void()> callback;//回调函数
    uint8_t cancel;//取消标志
    int id; 
};

struct List{
    TimerNode head;
    TimerNode*tail;
};

class TimerWheel{
public:
    TimerWheel(){

    }
    ~TimerWheel(){

    }

public:
    TimerNode *link_clear(List *list) {
        TimerNode *ret = list->head.next;
        list->head.next = 0;
        list->tail = &(list->head);
        return ret;
    }

    void link(List *list, TimerNode *node) {
        list->tail->next = node;
        list->tail = node;
        node->next = 0;
    }

    void add_node(TimerWheel*T, TimerNode *node) {//添加节点
        uint32_t time = node->expire;
        uint32_t current_time = T->time_;
        uint32_t msec = time - current_time;
        if (msec < TIME_NEAR) {//分层添加任务
            link(&T->near[time & TIME_NEAR_MASK], node);
        } else if (msec < (1 << (TIME_NEAR_SHIFT + TIME_LEVEL_SHIFT))) {
            link(&T->t[0][(time >> TIME_NEAR_SHIFT) & TIME_LEVEL_MASK], node);
        } else if (msec < (1 << (TIME_NEAR_SHIFT + 2 * TIME_LEVEL_SHIFT))) {
            link(&T->t[1][(time >> (TIME_NEAR_SHIFT + TIME_LEVEL_SHIFT)) & TIME_LEVEL_MASK], node);
        } else if (msec < (1 << (TIME_NEAR_SHIFT + 3 * TIME_LEVEL_SHIFT))) {
            link(&T->t[2][(time >> (TIME_NEAR_SHIFT + 2 * TIME_LEVEL_SHIFT)) & TIME_LEVEL_MASK], node);
        } else {
            link(&T->t[3][(time >> (TIME_NEAR_SHIFT + 3 * TIME_LEVEL_SHIFT)) & TIME_LEVEL_MASK], node);
        }
    }

    void move_list(TimerWheel *T, int level, int idx) {//清除链表并将所有定时器节点重新添加到时间轮中,以便重新分配。
        TimerNode *current = link_clear(&T->t[level][idx]);
        while (current) {
            TimerNode *temp = current->next;
            add_node(T, current);
            current = temp;
        }
    }

    void timer_shift(TimerWheel *T) {//更新时间轮的时间,并在必要时移动远程定时器链表中的定时器。
        int mask = TIME_NEAR;
        uint32_t ct = ++T->time_;
        if (ct == 0) {
            move_list(T, 3, 0);
        } else {
            uint32_t time = ct >> TIME_NEAR_SHIFT;
            int i = 0;
            while ((ct & (mask - 1)) == 0) {
                int idx = time & TIME_LEVEL_MASK;
                if (idx != 0) {//重新分配
                    move_list(T, i, idx);
                    break;
                }
                mask <<= TIME_LEVEL_SHIFT;
                time >>= TIME_LEVEL_SHIFT;
                ++i;
            }
        }
    }
    
    void dispatch_list(TimerNode *current) {//执行链表中的所有定时器节点,并释放它们。
        do {
            TimerNode *temp = current;
            current = current->next;
            if (temp->cancel == 0)
                temp->callback();
            free(temp);
        } while (current);
    }

    void timer_execute(TimerWheel *T) {//执行当前时间点的所有近程定时器。
        int idx = T->time_ & TIME_NEAR_MASK;
        while (T->near[idx].head.next) {
            TimerNode *current = link_clear(&T->near[idx]);
            std::lock_guard<std::mutex> lock(mutex_);
            dispatch_list(current);
        }
    }

    void *timer_update(TimerWheel *T) {//执行所有到期的定时器,并更新时间轮。
        std::lock_guard<std::mutex> lock(mutex_);
        timer_execute(T);
        timer_shift(T);
        timer_execute(T);
    }

    void del_timer(TimerNode *node) {
        node->cancel = 1;
    }

    uint64_t gettime() {//获取当前时间
	    uint64_t t;
#if !defined(__APPLE__) || defined(AVAILABLE_MAC_OS_X_VERSION_10_12_AND_LATER)
	    struct timespec ti;
	    clock_gettime(CLOCK_MONOTONIC, &ti);
	    t = (uint64_t)ti.tv_sec * 1000;
	    t += ti.tv_nsec / 1000000;
#else
	    struct timeval tv;
	    gettimeofday(&tv, NULL);
	    t = (uint64_t)tv.tv_sec * 100;
	    t += tv.tv_usec / 10000;
#endif
	    return t;
    }

    void expire_timer(void) {//检查是否有定时器已经过期。
        uint64_t cp = gettime();
        //首先获取当前时间,然后与时间轮中记录的最后检查时间 current_point 进行比较。
        //如果当前时间与最后检查时间不同,说明已经过去了一段时间,需要调用 timer_update 函数多次来处理这段时间内的所有过期定时器。
        if (cp != this->current_point_) {
            uint32_t diff = (uint32_t)(cp - this->current_point_);
            this->current_point_ = cp;
            int i;
            for (i=0; i<diff; i++) {
                timer_update(this);
            }
        }
    }

   void clear_timer() {
	    int i,j;
	    for (i=0;i<TIME_NEAR;i++) {
		    List * list = &this->near[i];
		    TimerNode* current = list->head.next;
		    while(current) {
			    TimerNode * temp = current;
			    current = current->next;
			    free(temp);
		    }
		    link_clear(&this->near[i]);
	    }
	    for (i=0;i<4;i++) {
		    for (j=0;j<TIME_LEVEL;j++) {
			    List * list = &this->t[i][j];
			    TimerNode* current = list->head.next;
			    while (current) {
				    TimerNode * temp = current;
				    current = current->next;
				    free(temp);
			    }
			    link_clear(&this->t[i][j]);
		    }
	    }
    }

private:
    List near[TIME_NEAR];//近程任务链表
    List t[4][TIME_LEVEL];//远程任务分四层链表
    std::mutex mutex_;
    uint32_t time_;//当前时间
    uint64_t current_;
    uint64_t current_point_;
};

标签:定时器,实现,TimerNode,time,list,current,时间,TIME
From: https://blog.csdn.net/oxygen3000/article/details/141963131

相关文章

  • Python在报表自动化的优势及实现流程
    Python在报表自动化的优势及实现流程 更新时间:2023年12月28日10:08:08 作者:涛哥聊Python  本文利用Python实现报表自动化,通过介绍环境设置、数据收集和准备、报表生成以及自动化流程,展示Python的灵活性和丰富的生态系统在报表自动化中的卓越表现,从设置虚拟环境到使......
  • Python毕业设计基于Django的图书借阅系统的设计与实现(源码+LW+部署讲解)
    文末获取资源,收藏关注不迷路文章目录一、项目介绍二、主要使用技术三、研究内容四、核心代码五、文章目录一、项目介绍本“期待相遇”图书借阅系统是为了提高用户查阅信息的效率和管理人员管理信息的工作效率,可以快速存储大量数据,还有信息检索功能,这大大的满足了......
  • springboot+vue乡村信息化管理系统的设计与实现【程序+论文+开题】计算机毕业设计
    系统程序文件列表开题报告内容研究背景随着信息技术的飞速发展,信息化已成为推动社会进步和治理现代化的重要力量。在乡村振兴战略的大背景下,乡村信息化管理系统的建设显得尤为重要。传统乡村管理模式面临着信息孤岛、效率低下、资源分配不均等问题,严重制约了乡村治理的现代......
  • 828华为云征文 | 华为云Flexus X实例上实现Docker容器的实时监控与可视化分析
    Docker容器监控之CAdvisor+InfluxDB+Granfana需要了解本文章主要讲述在华为云FlexusX实例上搭建开源的容器管理平台,使用的WebUI界面来简化和优化容器及集群的管理和监控选择合适的云服务器:本文采用的是华为云服务器FlexusX实例(推荐使用)连接方式:本文通过本地sh......
  • 使用libmpg123加alsa实现MP3的播放/暂停,切换,模式选择,C语言3
    note:使用多线程的方式MP3实现播放器,其中用到libmpg123,以及asound库,解码用到libmpg123,播放用到alsa,以下为c语言例程源码#include<alsa/asoundlib.h>#include<mpg123.h>#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<pthread.h>#include&l......
  • 基于mediapipe和pyttsx3技术实现一个姿态识别语音播报器
    系列文章目录第一章Python机器学习入门之mediapipe和pyttsx3的结合使用文章目录系列文章目录前言一、mediapipe和pyttsx3是什么?二、使用步骤1.引入库2.读入数据总结前言在比赛准备时,由于比赛任务要求需要机器人在自主迅游中记录家庭成员的行为动作,并进行语音播报......
  • 五子棋AI 任务1:实现棋盘类
    绪论本篇将引导读者如何构建一个五子棋棋盘类,并且在结尾给出了已经写好关键接口的类定义,使得读者将注意力聚焦在功能的实现上。下载代码文件任务要求详解对于需要填写的部分,用#define语句定义宏进行了替代,以保证通过编译,在编写代码时删掉即可。#defineQUEST_BOOLtrue#d......
  • c++元对象实现
    c++元对象实现在C++中,元对象技术通常指的是运行时检查类型信息和对象信息的能力。C++11标准引入了typetraits和reflection的概念,允许我们在编译时获取和使用类型信息。下面是一个简单的C++类,使用了C++11的typetraits和C++17的std::any来实现元对象:  #include<iostrea......
  • SpringBoot集成WebSocket实现后端向前端推送数据
    SpringBoot集成WebSocket实现后端向前端推送数据这里最好了解一定websocket参考地址:https://developer.mozilla.org/zh-CN/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications在此之前可以了解一下【轮询(Polling)、长轮询(LongPolling)、服务器发送事件(Server......
  • 基于大数据+爬虫+数据可视化的的​媒体社交与可视化平台平台设计和实现(源码+LW+部署
     博主介绍:✌全网粉丝50W+,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌技术范围:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs......