时间堆定时器

Posted qq_34132502

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了时间堆定时器相关的知识,希望对你有一定的参考价值。

前面讨论的定时方案都是以固定的频率调用心搏函数tick,并在其中依次检测到期的定时器,然后执行到期定时器上的回调函数。设计定时器的另外一种思路是:将所有定时器中超时时间最小的一个定时器的超时值作为心搏间隔。这样,一旦心搏函数tick被调用,超时时间最小的定时器必然到期,我们就可以在tick函数中处理该定时器。然后,再次从剩余的定时器中找出超时时间最小的一个, 并将这段最小时间设置为下一次心搏间隔。如此反复,就实现了较为精确的定时。

最小堆很适合处理这种定时方案。

由于最小堆是一种完全二叉树,所以我们可以用数组来组织其中的元素。
对于数组中的任意一个位置i上的元素,其左儿子节点在位置2i+1上,其右儿子节点在位置2i+2上,其父节点则在位置[(i - 1) / 2]上。与用链表来表示堆相比,用数组表示堆不仅节省空间,而且更容易实现堆的插人、删除等操作。
在这里插入图片描述
在这里插入图片描述

#ifndef MIN_HEAP
#define MIN_HEAP

#include <iostream>
#include <netinet/in.h>
#include <time.h>
using std::exception;

#define BUFFER_SIZE     64
class heap_timer;

// 绑定socket和定时器
class client_data {
public:
    sockaddr_in address;
    int sockfd;
    char buf[BUFFER_SIZE];
    heap_timer* timer;
};

// 定时器类
class heap_timer {
public:
    heap_timer(int delay) {
        expire = time(NULL) + delay;
    }

    time_t expire;                  // 定时器生效的绝对时间
    void (*cb_func) (client_data*); // 定时器回调函数
    client_data* user_data;         // 客户数据
};

// 时间堆类
class time_heap{
public:
    // 构造函数之一,是初始化一个大小为cap的空堆
    time_heap(int cap) throw (std::exception) : capacity(cap), cur_size(0){
        array = new heap_timer* [capacity];
        if (!array) throw std::exception();
        for (int i = 0; i < capacity; i++) array[i] = NULL;
    }

    // 构造函数之二,用已有的数组来初始化堆
    time_heap(heap_timer** init_array, int size, int capacity)
        throw (std::exception): cur_size(size), capacity(capacity) {
        if (capacity < size) throw std::exception();
        array = new heap_timer* [capacity]; // 创建数组
        if (!array) throw std::exception();
        for (int i = 0; i < capacity; i++) array[i] = NULL;
        if (size != 0) {
            // 初始化堆数组
            for (int i = 0; i < size; i++) array[i] = init_array[i];
            // 对数组中的第[(cur_size - 1) / 2] ~ 0 个元素执行下滤操作
            for (int i = (cur_size - 1) / 2; i >=0; --i) percolate_down(i);
        }
    }

    // 销毁时间堆
    ~time_heap() {
        for (int i = 0; i < cur_size; ++i) delete array[i];
        delete [] array;
    }

public:
    // 添加目标定时器timer
    void add_timer(heap_timer* timer) throw (std::exception) {
        if (!timer) return;
        // 如果当前数组容量不够
        if (cur_size >= capacity) resize();
        // 新插入了一个元素,当前堆大小加1,hole是新建空穴的位置
        int hole = cur_size++;
        int parent = 0;
        // 对空穴到根节点的路径上所有的节点执行上滤操作
        for (; hole > 0; hole = parent) {
            parent = (hole - 1) / 2;
            if (array[parent]->expire <= timer->expire) break;
            array[hole] = array[parent];
        }
        array[hole] = timer;
    }

    // 删除目标定时器timer
    void del_timer(heap_timer* timer) {
        if (!timer) return ;
        // 仅仅将目标定时器的回调函数设置为空,即所谓的延迟销毁
        // 这将节省真正删除改定时器造成的开销,但这样做容易使堆数组膨胀
        timer->cb_func = NULL;
    }

    // 获得堆顶部的定时器
    heap_timer* top() const {
        if (empty()) return ;
        return array[0];
    }

    // 删除堆顶部的定时器
    void pop_timer() {
        if (empty()) return ;
        if (array[0]) {
            delete array[0];
            // 将原来的堆顶元素替换为堆数组中最后一个元素
            array[0] = array[--cur_size];
            // 对新的堆顶元素执行下滤操作
            percolate_down(0);
        }
    }

    // 心跳函数
    void tick() {
        heap_timer* tmp = array[0];
        // 循环处理堆中到期的定时器
        time_t cur = time(NULL);
        while (!empty()) {
            if (!tmp) return;
            // 如果堆顶定时器没有到期,则会出循环
            if (tmp->expire > cur) break;
            // 否则就执行堆顶定时器中的任务
            if (array[0]->cb_func) array[0]->cb_func(array[0]->user_data);
            // 将堆顶元素删除,同时生成新的堆顶定时器(array[0])
            pop_timer();
            tmp = array[0];
        }
    }

    bool empty() const {
        return cur_size == 0;
    }


private:
    // 最小堆的下滤操作,它确保堆数组中以第hole个节点最为根的子树拥有最小堆的性质
    void percolate_down(int hole) {
        heap_timer* temp = array[hole];
        int child = 0;
        for (; ((hole * 2 + 1) <= (cur_size - 1)); hole = child) {
            child = hole * 2 + 1;
            if ((child < (cur_size - 1)) && (array[child + 1]->expire < array[child]->expire)) ++child;
            if (array[child]->expire < temp->expire) array[hole] = array[child];
            else break;
        }
        array[hole] = temp;
    }

    // 将堆数组容量扩大一倍
    void resize() throw (std::exception) {
        heap_timer** temp = new heap_timer* [2 * capacity];
        for (int i = 0; i < 2 * capacity; i++) temp[i] = NULL;
        if (!temp) throw std::exception();
        capacity *= 2;
        for (int i = 0; i < cur_size; i++) temp[i] = array[i];
        delete [] array;
        array = temp;
    }

private:
    heap_timer** array; // 堆数组
    int capacity;       // 堆数组的容量
    int cur_size;       // 堆数组当前包含元素的个数
};

#endif

以上是关于时间堆定时器的主要内容,如果未能解决你的问题,请参考以下文章

时间堆定时器

Linux(程序设计):64---高性能定时器之时间堆

堆 在游戏中的运用

测试片段不执行定时器或示例超时

前端面试题之手写promise

「linux」定时器方案:红黑树最小堆和时间轮的原理