首页 > 其他分享 >定时器实现之最小堆(一)

定时器实现之最小堆(一)

时间:2024-12-02 18:33:25浏览次数:9  
标签:index 定时器 min 实现 最小 timer heap entry hole

1.概述

        定时器是一种用于在指定时间后执行特定任务或操作的机制。在计算机科学和编程中,定时器通常用于实现延时操作、周期性任务调度等功能。

         对于定时器的实现,核心分为两大块,分别是容器和触发方式,容器就是组织定时器中定时任务的数据结构,触发方式描述了我们以什么方式检测任务是否到期。今天的重点是定时器系列的开篇,能组织定时器的数据结构都要有一个基本的要求,就是能保证数据在数据结构内是有序的,通常的数据结构又最小堆、红黑树、时间轮等,本文将使用最小堆来组织定时器。

2.最小堆

        最小堆(Min-Heap)是一种特殊的完全二叉树,其中每个节点的值都小于或等于其子节点的值。这种特性使得最小堆的根节点总是存储着最小的元素。最小堆常用于实现需要快速访问最小元素的应用场景。 

        最小堆是一棵完全二叉树,这意味着除了最后一层外,其他所有层都是满的,并且最后一层的节点尽可能靠左排列。最小堆通常使用数组表示,其堆序性质:对于任意节点 ( i ),其子节点 ( 2i+1 ) 和 ( 2i+2 ) 满足以下条件:

  • heap[i] > heap[2i + 1]及父节点大于左子节点
  • heap[i] < heap[2i + 2]及父节点小于右子节点

3.代码实现 

         定义以下结构:

struct timer_entry {
    uint64_t time;            // 时间戳,用来表示定时任务的过期时间
    uint32_t min_heap_idx;    // 当前节点在堆(数组)中的索引
};

struct min_heap {
    timer_entry** p;
    uint32_t n; // n 为实际元素个数  
    uint32_t a; // a 为容量
};

         实现其核心调整算法:

#define min_heap_elem_greater(a, b) \
    ((a)->time > (b)->time)

static void min_heap_shift_up_(min_heap* s, uint32_t hole_index, timer_entry* e) {
    //计算父节点索引
    uint32_t parent = (hole_index - 1) / 2;
    //当前插入位置的索引不为0,并且插入位置的父节点的值比当前插入的值大,则交换位置知道这个条件不满足就把插入的元素插入到当前位置
    while (hole_index && min_heap_elem_greater(s->p[parent], e))
    {
        (s->p[hole_index] = s->p[parent])->min_heap_idx = hole_index;
        hole_index = parent;
        parent = (hole_index - 1) / 2;
    }
    (s->p[hole_index] = e)->min_heap_idx = hole_index;
}

static void min_heap_shift_down_(min_heap* s, uint32_t hole_index, timer_entry* e)
{
    // 计算右子节点的索引
    uint32_t min_child = 2 * (hole_index + 1);
    // 如果右子节点存在则继续向下调整
    while (min_child <= s->n)
    {
        // 确保指向最小的子节点
        min_child -= (min_child == s->n || min_heap_elem_greater(s->p[min_child], s->p[min_child - 1]));
        // 如果最小的子节点还是小于尾节点则不调整直接交换,否则继续向下寻找
        if (!(min_heap_elem_greater(e, s->p[min_child]))) {
            break;
        }
            
        (s->p[hole_index] = s->p[min_child])->min_heap_idx = hole_index;
        hole_index = min_child;
        min_child = 2 * (hole_index + 1);
    }
    (s->p[hole_index] = e)->min_heap_idx = hole_index;
}

        最后对最小堆的一些行为进行封装实现,下面是完整代码:

//minheap.h

#pragma once

#include <cstdint>

struct timer_entry {
    uint64_t time;            // 时间戳,用来表示定时任务的过期时间
    uint32_t min_heap_idx;    // 当前节点在堆(数组)中的索引
};

struct min_heap {
    timer_entry** p;
    uint32_t n; // n 为实际元素个数  
    uint32_t a; // a 为容量
};

//初始化最小堆
int min_heap_init(min_heap** h, uint32_t a);

//销毁最小堆
int min_heap_free(min_heap* h);

//往最小堆中插入元素
int min_heap_push(min_heap* h, timer_entry* x);

//往最小堆中拿出最小元素
int min_heap_pop(min_heap* h, timer_entry** x);

//获取堆顶元素(不会从堆中删除)
int min_heap_top(min_heap* h, timer_entry** x);
// minheap.cpp

#include "minheap.h"

#include <cstdlib>

#define min_heap_elem_greater(a, b) \
    ((a)->time > (b)->time)

static void min_heap_shift_up_(min_heap* s, uint32_t hole_index, timer_entry* e) {
    //计算父节点索引
    uint32_t parent = (hole_index - 1) / 2;
    //当前插入位置的索引不为0,并且插入位置的父节点的值比当前插入的值大,则交换位置知道这个条件不满足就把插入的元素插入到当前位置
    while (hole_index && min_heap_elem_greater(s->p[parent], e))
    {
        (s->p[hole_index] = s->p[parent])->min_heap_idx = hole_index;
        hole_index = parent;
        parent = (hole_index - 1) / 2;
    }
    (s->p[hole_index] = e)->min_heap_idx = hole_index;
}

static void min_heap_shift_down_(min_heap* s, uint32_t hole_index, timer_entry* e)
{
    // 计算右子节点的索引
    uint32_t min_child = 2 * (hole_index + 1);
    // 如果右子节点存在则继续向下调整
    while (min_child <= s->n)
    {
        // 确保指向最小的子节点
        min_child -= min_child == s->n || min_heap_elem_greater(s->p[min_child], s->p[min_child - 1]);
        // 如果最小的子节点还是小于尾节点则不调整直接交换,否则继续向下寻找
        if (!(min_heap_elem_greater(e, s->p[min_child]))) {
            break;
        }
            
        (s->p[hole_index] = s->p[min_child])->min_heap_idx = hole_index;
        hole_index = min_child;
        min_child = 2 * (hole_index + 1);
    }
    (s->p[hole_index] = e)->min_heap_idx = hole_index;
}

// 堆大小动态增长
static int min_heap_reserve(min_heap* s, uint32_t n) {
    if (s->a < n)
    {
        timer_entry** p;
        unsigned a = s->a ? s->a * 2 : 8;
        if (a < n)
            a = n;
        if (!(p = (timer_entry**)realloc(s->p, a * sizeof(*p)))) {
            return -1;
        }
            
        s->p = p;
        s->a = a;
    }
    return 0;
}

int min_heap_init(min_heap** h, uint32_t a) {

    *h = (min_heap*)malloc(sizeof(min_heap));
    if(!*h) {
        return -1;
    }

    (*h)->p = (timer_entry**)malloc(a * sizeof(*((*h)->p)));
    if(!(*h)->p) {
        return -1;
    }

    (*h)->a = a;
    (*h)->n = 0;
    return 0;
}

int min_heap_free(min_heap* h) {
    if(h) {
        return -1;
    }

    if(h->p) {
        free(h->p);
    }

    free(h);
    return 0;
}

int min_heap_push(min_heap* h, timer_entry* x) {
    if(min_heap_reserve(h, h->n + 1)) {
        return -1;
    }

    min_heap_shift_up_(h, h->n++, x);
    return 0;
}

int min_heap_pop(min_heap* h, timer_entry** x) {
    if (h->n)
    {
        timer_entry* e = *(h->p);
        min_heap_shift_down_(h, 0, h->p[--h->n]);
        e->min_heap_idx = -1;
        *x = e;
        return 0;
    }
    return -1;
}

int min_heap_top(min_heap* h, timer_entry** x) {
    if (h->n)
    {
        timer_entry* e = *(h->p);
        *x = e;
        return 0;
    }
    return -1;
}

4.封装测试

        细心的同学可能注意到,上述最小堆里没有任何业务相关的东西,貌似就是一个什么都不做的最小堆,但看过linux内核的同学应该都知道我要怎么做了,话不多说上代码:

//main.cpp

#include "minheap.h"

#include <cstdint>
#include <iostream>
#include <functional>
#include <chrono>
#include <ctime>
#include <thread>

extern "C"{
#define offsetofs(s,m) (size_t)(&reinterpret_cast<const volatile char&>((((s*)0)->m)))
#define container_of(ptr, type, member) ({                  \
    const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
    (type *)( (char *)__mptr - offsetofs(type, member) );})
}

using CallBack = std::function<void(void)>;

struct TimeNode {
    timer_entry env;
    CallBack cb;
};

class Timer {
public:
    Timer(uint32_t size);
    Timer() = delete;
    ~Timer();

    int addTimer(uint64_t time, CallBack cb);

    void run();

    void stop();

private:
    uint64_t GetNowTime();
private:
    min_heap* _heap;
    bool _close;
};

Timer::Timer(uint32_t size) {
    min_heap_init(&_heap, size);
    _close = false;
}

Timer::~Timer() {
    min_heap_free(_heap);
}

int Timer::addTimer(uint64_t time, CallBack cb) {
    TimeNode* node = new TimeNode();
    node->env.time = GetNowTime() + time;
    node->cb = cb;
    min_heap_push(_heap, &node->env);
    return 0;
}

uint64_t Timer::GetNowTime() {
    auto now = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch());
    uint64_t timestamp_ms = duration.count();
    return timestamp_ms;
}

void Timer::run() {
    while(!_close) {
        uint64_t now = GetNowTime();
        timer_entry* entry = nullptr;
        if(!min_heap_top(_heap, &entry)) {
            if(entry->time <= now) {
                if(!min_heap_pop(_heap, &entry)) {
                    TimeNode* node = container_of(entry, TimeNode, env);
                    if(node) {
                        node->cb();
                    }

                    delete node;
                }
            } else {
                std::this_thread::sleep_for(std::chrono::milliseconds(entry->time - now));
            }
        }

        std::this_thread::sleep_for(std::chrono::milliseconds(50));
    }
}

void Timer::stop() {
    _close = true;
}

int main(int, char**){

    Timer timer(10);

    auto now = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch());
    uint64_t timestamp_ms = duration.count();
    std::cout << "Start, now time :" << timestamp_ms << std::endl;
    timer.addTimer(1000, [&](void){
        auto now = std::chrono::high_resolution_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch());
        uint64_t timestamp_ms = duration.count();
        std::cout << "timer 1, now time :" << timestamp_ms << std::endl;
    });

    timer.addTimer(2000, [&](void){
        auto now = std::chrono::high_resolution_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch());
        uint64_t timestamp_ms = duration.count();
        std::cout << "timer 2, now time :" << timestamp_ms << std::endl;
    });

    std::thread t([&](){
        timer.run();
    });
    std::this_thread::sleep_for(std::chrono::milliseconds(3000));
    timer.stop();
    t.join();

    return 0;
}

         我的目标是实现一个通用定时器,里面包含所有定时器的实现方便选择,所以我使用了cmake加clangd加vscode来管理我的工程,目前我的目录结构如下:

         ok,现在编译运行看结果

学习参考:0voice · GitHub    

标签:index,定时器,min,实现,最小,timer,heap,entry,hole
From: https://blog.csdn.net/weixin_55951019/article/details/144171720

相关文章

  • 记录一个前端景深效果的实现
    参考教程:https://blog.csdn.net/aaaa_aaab/article/details/143949881在上述教程的基础上有一些修改,并非是在banner上的应用:展示代码tsimporttype{CSSProperties}from'vue'conststartX=ref(0);constcurrentX=ref(0);constcloudStyle1=ref<CSSPropertie......
  • muduo实现ssl层的程序设计
    muduo的使用muduo网络库内部分装了reactor和epoll以及socket,我们不需要知道其底层的内部封装;每次发生连接后都会调用连接onConnection的回调函数来处理连接。每次当数据到达时都会调用onmessagecallback回调函数来执行数据的处理。muduo增加ssl层根据上一节,我们可以设计一个ss......
  • springboot框架下基于Java Web的新能源汽车信息咨询系统设计与实现
    内容概要:本文介绍了基于springboot框架和JavaWeb技术的新能源汽车信息咨询服务的设计与实现。系统采用B/S架构,使用MySQL数据库,旨在提高用户体验,简化管理和操作流程。系统主要功能包括个人信息管理、品牌类别管理、新能源汽车信息展示、汽车订单和配送订单管理等,还增加了首页推......
  • node.js毕设美食菜谱推广与互动系统的设计与实现 程序+论文
    本系统(程序+源码+数据库+调试部署+开发环境)带文档lw万字以上,文末可获取源码系统程序文件列表开题报告内容一、选题背景关于美食菜谱推广与互动系统的设计与实现这一课题,现有研究主要以美食菜谱的简单展示或单一功能开发为主,如部分研究聚焦于单纯的菜谱分享平台或基础的美......
  • 玩转RK3588开发板基于connector-split 功能实现多屏联动
     什么是多屏拼接显示?多屏拼接显示就是把几个显示器(比如MIPI屏幕、HDMI屏幕或者DP屏幕)拼接显示,把它们变成一个大屏幕。如会议室是拼接屏的主要应用场景之一。在会议室中,拼接屏可以用于显示会议议程、演示资料、视频会议等。拼接屏可以将多个屏幕拼接在一起,形成一个大屏幕,使得参......
  • C# 继承的实现
    //创建一个Person的父类publicclassPerson{privatestring_name;publicstringName{get{return_name;}set{_name=value;}}privateint_age;publicintAge{get{return_age;}set{_age=value;}......
  • 一种C#原生实现的神经网络
    矩阵运算部分来源:https://www.cnblogs.com/gpcuster/archive/2008/05/22/1204456.html这位坛友是我用C#设计神经网络的引路人,虽然Matrix类也是简简单单的double[,],但是至少让各种运算能进行了。提供了一个例子,我发现这个例子没有偏置,而且较难添加隐含层,于是进行了一通爆改,改成了T......
  • el-tree 实现动态初始默认选中和全选
    <template><div><!--全选选择框--><el-checkboxv-model="checkAll":indeterminate="isIndeterminate"@change="handleCheckAllChange">全选</el-checkbox>......
  • 基于SpringBoot的中小企业设备管理系统的设计与实现(源码+SQL脚本+LW+部署讲解等)
    专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。主要内容:免费功能设计、开题报告、任务书、中......
  • 基于SpringBoot的论坛网站系统的设计与实现(源码+SQL脚本+LW+部署讲解等)
    专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。主要内容:免费功能设计、开题报告、任务书、中......