002. 使用最小堆实现高性能定时器实现

这篇具有很好参考价值的文章主要介绍了002. 使用最小堆实现高性能定时器实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一. 原理

  • 定时器原理
    – 任务的容器(要求:数据结构有序或相对有序;能快速查找最近触发的定时任务) + 触发条件(可以通过带有time_out的系统函数,如epoll_wait的最后一个参数);
  • 最小堆的特点
    – 是一颗完全二叉树;
    – 某个节点的值总是小于等于它子节点的值(即定位到最小值的时间复杂度为O(1),这点可以作为定时器的容器);
    – 堆中任意一个节点的子树都是最小堆;
  • 最小堆添加节点
    – ① 向二叉树的最高层的最左侧添加一个节点;
    – ② 然后考虑上升操作(即和父节点比较,如果值大于父节点,就交换二者位置);
  • 最小堆删除节点
    – ① 在堆中找到要删除的节点;
    – ② 将①中找到的节点和最高层的最左侧节点交换;
    – ③ 先考虑交换后的节点能否执行下降操作,否则执行上升操作;
    – ④ 删除最后一个节点;

二. 案例1:在linux平台下纯C代码实现定时器;

minheap.h

#ifndef MARK_MINHEAP_H
#define MARK_MINHEAP_H

#include <stdint.h>
#include <stdlib.h>

typedef struct timer_entry_s timer_entry_t;
typedef void (*timer_handler_pt)(timer_entry_t *ev);

struct timer_entry_s {
    uint32_t time;
    uint32_t min_heap_idx;
    timer_handler_pt handler;
    void *privdata;
};

typedef struct min_heap {
    timer_entry_t **p; // 二维指针,指向一维指针的地址,所以此处其实就是执行了一个以为数组
    uint32_t n, a; // n 为实际元素个数  a 为容量
} min_heap_t;

void            min_heap_ctor_(min_heap_t* s);
void            min_heap_dtor_(min_heap_t* s);
void            min_heap_elem_init_(timer_entry_t* e);
int             min_heap_elt_is_top_(const timer_entry_t *e);
int             min_heap_empty_(min_heap_t* s);
unsigned        min_heap_size_(min_heap_t* s);
timer_entry_t*  min_heap_top_(min_heap_t* s);
int             min_heap_reserve_(min_heap_t* s, unsigned n);
int             min_heap_push_(min_heap_t* s, timer_entry_t* e);
timer_entry_t*  min_heap_pop_(min_heap_t* s);
int             min_heap_adjust_(min_heap_t *s, timer_entry_t* e);
int             min_heap_erase_(min_heap_t* s, timer_entry_t* e);
void            min_heap_shift_up_(min_heap_t* s, unsigned hole_index, timer_entry_t* e);
void            min_heap_shift_up_unconditional_(min_heap_t* s, unsigned hole_index, timer_entry_t* e);
void            min_heap_shift_down_(min_heap_t* s, unsigned hole_index, timer_entry_t* e);

#endif // MARK_MINHEAP_H

minheap.c

#include "minheap.h"

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

// 创建最小堆
void min_heap_ctor_(min_heap_t* s) { s->p = 0; s->n = 0; s->a = 0; }

// 
void min_heap_dtor_(min_heap_t* s) { if (s->p) free(s->p); }
void min_heap_elem_init_(timer_entry_t* e) { e->min_heap_idx = -1; }
int min_heap_empty_(min_heap_t* s) { return 0u == s->n; }
unsigned min_heap_size_(min_heap_t* s) { return s->n; }
timer_entry_t* min_heap_top_(min_heap_t* s) { return s->n ? *s->p : 0; }

int min_heap_push_(min_heap_t* s, timer_entry_t* e)
{
    if (min_heap_reserve_(s, s->n + 1))
        return -1; // 如果空间分配失败,则返回,程序终止。
    // 插入新任务,并且上升调整。
    min_heap_shift_up_(s, s->n++, e);
    return 0;
}

timer_entry_t* min_heap_pop_(min_heap_t* s)
{
    if (s->n)
    {
        timer_entry_t* e = *s->p;
        // s->p[--s->n]是该堆的最后一个元素,相当于将最后一个元素填入到最小元素的位置,而后进行下降操作。
        min_heap_shift_down_(s, 0u, s->p[--s->n]);
        e->min_heap_idx = -1;
        return e;
    }
    return 0;
}

int min_heap_elt_is_top_(const timer_entry_t *e)
{
    return e->min_heap_idx == 0;
}

int min_heap_erase_(min_heap_t* s, timer_entry_t* e)
{
    if (-1 != e->min_heap_idx)
    {
        timer_entry_t *last = s->p[--s->n];
        unsigned parent = (e->min_heap_idx - 1) / 2;
        /* we replace e with the last element in the heap.  We might need to
           shift it upward if it is less than its parent, or downward if it is
           greater than one or both its children. Since the children are known
           to be less than the parent, it can't need to shift both up and
           down. */
        if (e->min_heap_idx > 0 && min_heap_elem_greater(s->p[parent], last))
            min_heap_shift_up_unconditional_(s, e->min_heap_idx, last);
        else
            min_heap_shift_down_(s, e->min_heap_idx, last);
        e->min_heap_idx = -1;
        return 0;
    }
    return -1;
}

int min_heap_adjust_(min_heap_t *s, timer_entry_t *e)
{
    if (-1 == e->min_heap_idx) {
        return min_heap_push_(s, e);
    } else {
        unsigned parent = (e->min_heap_idx - 1) / 2;
        /* The position of e has changed; we shift it up or down
         * as needed.  We can't need to do both. */
        if (e->min_heap_idx > 0 && min_heap_elem_greater(s->p[parent], e))
            min_heap_shift_up_unconditional_(s, e->min_heap_idx, e);
        else
            min_heap_shift_down_(s, e->min_heap_idx, e);
        return 0;
    }
}

int min_heap_reserve_(min_heap_t* s, unsigned n)
{
    // 当需要插入新的任务时,如果当前容量小于插入后的任务数量,执行如下if操作,申请内存空间。
    if (s->a < n)
    {
        // 相当于声明了一个*p[],即一个指向一个timer_entry_t数组的指针。
        timer_entry_t** p; 
        // 若容量==0,初始申请8个节点,若容量!=0,初始申请原来容量的2倍。
        unsigned a = s->a ? s->a * 2 : 8;
        // [MJ]不知道干嘛的?
        if (a < n)
            a = n;

        // 重新开辟一个容量为a的一维数组,数组中的每个元素指向一个timer_entry_s。
        if (!(p = (timer_entry_t**)realloc(s->p, a * sizeof *p)))
            return -1;
        
        // 将新开辟的地址赋值给min_heap_t中指向的一维数组的地址。
        s->p = p;
        // 更新容量。
        s->a = a;
    }
    return 0;
}

void min_heap_shift_up_unconditional_(min_heap_t* s, unsigned hole_index, timer_entry_t* e)
{
    unsigned parent = (hole_index - 1) / 2;
    do
    {
    (s->p[hole_index] = s->p[parent])->min_heap_idx = hole_index;
    hole_index = parent;
    parent = (hole_index - 1) / 2;
    } while (hole_index && min_heap_elem_greater(s->p[parent], e));
    (s->p[hole_index] = e)->min_heap_idx = hole_index;
}

void min_heap_shift_up_(min_heap_t* s, unsigned hole_index, timer_entry_t* e)
{
    // hole_index:新插入元素所在的位置下标
    unsigned parent = (hole_index - 1) / 2;
    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;
    }
    // 最后将新插入的节点放入到对应的位置,并且设置对应的在堆中的index
    (s->p[hole_index] = e)->min_heap_idx = hole_index;
}

void min_heap_shift_down_(min_heap_t* s, unsigned hole_index, timer_entry_t* e)
{
    // hole_index 为开始下沉的位置。e是该堆的最后一个元素

    unsigned min_child = 2 * (hole_index + 1); // 右孩子
    while (min_child <= s->n)
    {
        // 如果当前min_child是堆的最后一个值或者当前值比左子树值大,则min_child指向左子树,也就是指向小的。
        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;
}

mh-timer.h

#ifndef MARK_MINHEAP_TIMER_H
#define MARK_MINHEAP_TIMER_H

#if defined(__APPLE__)
#include <AvailabilityMacros.h>
#include <sys/time.h>
#include <mach/task.h>
#include <mach/mach.h>
#else
#include <time.h>
#endif

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>

#include "minheap.h"

static min_heap_t min_heap; // 声明最小堆

static uint32_t
current_time() {
	uint32_t t;
#if !defined(__APPLE__) || defined(AVAILABLE_MAC_OS_X_VERSION_10_12_AND_LATER)
	struct timespec ti;
	clock_gettime(CLOCK_MONOTONIC, &ti);
	t = (uint32_t)ti.tv_sec * 1000;
	t += ti.tv_nsec / 1000000;
#else
	struct timeval tv;
	gettimeofday(&tv, NULL);
	t = (uint32_t)tv.tv_sec * 1000;
	t += tv.tv_usec / 1000;
#endif
	return t;
}

void init_timer() {
    min_heap_ctor_(&min_heap);
}

// 向定时器中添加任务
timer_entry_t * add_timer(uint32_t msec, timer_handler_pt callback) {
    timer_entry_t *te = (timer_entry_t *)malloc(sizeof(*te));
    if (!te) {
        return NULL;
    }
    memset(te, 0, sizeof(timer_entry_t));

    te->handler = callback;
    te->time = current_time() + msec;

    if (0 != min_heap_push_(&min_heap, te)) {
        // 如果push失败,则释放创建的最小堆,结束程序;
        free(te);
        return NULL;
    }
    printf("add timer time = %u now = %u\n", te->time, current_time());
    return te;
}

bool del_timer(timer_entry_t *e) {
    return 0 == min_heap_erase_(&min_heap, e);
}

int find_nearest_expire_timer() {
    timer_entry_t *te = min_heap_top_(&min_heap);
    if (!te) return -1;
    int diff = (int) te->time - (int)current_time();
    return diff > 0 ? diff : 0;
}

void expire_timer() {
    uint32_t cur = current_time();
    for (;;) {
        // 如果到目标时间,则会将该时间点的未执行的任务都执行了。
        timer_entry_t *te = min_heap_top_(&min_heap);
        if (!te) break;
        if (te->time > cur) break; // 还未到处理时间
        te->handler(te);
        min_heap_pop_(&min_heap);
        free(te); // 释放节点任务
    }
}

#endif // MARK_MINHEAP_TIMER_H

mh-timer.c

#include <stdio.h>
#include <sys/epoll.h>
#include "mh-timer.h"

// 任务函数
void hello_world(timer_entry_t *te) {
    printf("hello world time = %u\n", te->time);
}

int main() {
    init_timer(); // 初始化定时器。

    //参数1的单位为ms
    add_timer(3000, hello_world); // 向定时器中添加任务。
    add_timer(3000, hello_world); // 向定时器中添加任务。
    add_timer(3001, hello_world); // 向定时器中添加任务。
    add_timer(9000, hello_world); // 向定时器中添加任务。

    int epfd = epoll_create(1);
    struct epoll_event events[512];

    for (;;) {
        int nearest = find_nearest_expire_timer(); // 找到最近到期的任务。
        int n = epoll_wait(epfd, events, 512, nearest); // 成功返回需要处理的事件数目。失败返回0,表示等待超时
        for (int i=0; i < n; i++) {
            // 
        }
        expire_timer();
    }
    return 0;
}

// gcc mh-timer.c minheap.c -o mh -I./

运行命令

gcc mh-timer.c minheap.c -o mh -I./
./mh

文章来源地址https://www.toymoban.com/news/detail-471372.html

到了这里,关于002. 使用最小堆实现高性能定时器实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Docker与Kafka:实现高性能流处理

    Docker 和 Kafka 都是现代技术中的重要组成部分,它们各自在不同领域发挥着重要作用。Docker 是一个开源的应用容器引擎,用于自动化部署、创建、运行和管理应用程序。Kafka 是一个分布式流处理平台,用于构建实时数据流管道和流处理应用程序。 在大数据和实时数据处理领域

    2024年02月20日
    浏览(54)
  • uni-app如何实现高性能

    这篇文章主要讲解uni-app如何实现高性能的问题? 什么是uni-app? 简单说一下什么是uni-app,uni-app是继承自vue.js,对vue做了轻度定制,并且实现了完整的组件化开发,并且支持多端发布的一种架构,开发的项目可适配多平台。 过内前端开发的大致分歧  国内前端开发生态现在的

    2024年04月11日
    浏览(41)
  • 移动端高性能Unity播放器实现方案

    前情提要: 视听体验再进化——如何在24小时内全面升级你的视频应用 如何打造新时代的终端播放产品? 随着VR、AR、元宇宙等新玩法的出现,Unity平台的视频播放需求逐渐增加,比如下面两个动图就是在百度真实的案例。前者是演唱会场景,后者则是一个演讲会场。 通过这

    2024年02月08日
    浏览(43)
  • kafka高吞吐、低延时、高性能的实现原理

    作者:源码时代-Raymon老师 Kafka是大数据领域无处不在的消息中间件,目前广泛使用在企业内部的实时数据管道,并帮助企业构建自己的流计算应用程序。Kafka虽然是基于磁盘做的数据存储,但却具有高性能、高吞吐、低延时的特点,其吞吐量动辄几万、几十上百万,这其中的

    2024年02月04日
    浏览(55)
  • C++高性能服务器网络框架设计与实现

    这篇文章将从两个方面来介绍,一个是服务器中的基础的网络通信部件;另外一个是,如何利用这些基础通信部件整合成一个完整的高效的服务器框架。注意:本文以下内容中的客户端是相对概念,指的是连接到当前讨论的服务程序的终端,所以这里的客户端既可能是我们传

    2024年02月04日
    浏览(54)
  • Unity背景模糊图片高斯模糊高性能的实现方案

    环境: unity2021.3.x 效果: 模糊前: 模糊后: 模糊前: 模糊后: 实现核心思路(shader): github地址:高斯模糊 Github地址

    2024年04月26日
    浏览(37)
  • Redis实现高性能的全文搜索引擎---RediSearch

    RediSearch是一个Redis模块,为Redis提供查询、二次索引和全文搜索,他的性能甚至比es还要高。 注意端口号不要和redis冲突了:

    2024年02月16日
    浏览(61)
  • 高性能计算的矩阵乘法优化 - Python + OpenMP实现

    关于上一节读者某些疑问 :为什么你用进程并行不是线程并行? 回答 :由于Python解释器有GIL(全局解释器锁),在单进程的解释器上有线程安全锁,也就是说每次只能一个线程访问解释器,因此Python在语法上的多线程(multithreads)实现是不会提高并行性能的。 这一点和C

    2024年02月15日
    浏览(69)
  • c++代码实现一个高性能内存池(超详细版本)

    写在前面 本文的内存池代码是改编自Nginx的内存池源码,思路几乎一样。由于Nginx源码的变量命名我不喜欢,又没有注释,看得我很难受。想自己写一版容易理解的代码。这应该是全网独一份的超详细版本了(笑~). 应用场景 写内存池的原理之前,按照惯例先说内存池的应用

    2024年02月06日
    浏览(50)
  • 高性能计算的矩阵乘法优化 - Python +MPI的实现

    本次实验的目的是使用MPI的并行性来进行矩阵乘法优化,本人使用 Python 实现 实验硬件: CPU :AMD Ryzen 7 5800H(3.20 GHz) 内存 :32GB (3200MHz) 要求 :使用一个矩阵,一个向量相乘,分别用单进程和多进程的mpi接口实现。 全局的规模参数是 Scale 数据示例 : 当 Scale=5 时,数据示例如

    2023年04月22日
    浏览(103)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包