时间堆定时器
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
以上是关于时间堆定时器的主要内容,如果未能解决你的问题,请参考以下文章