定时器之最小堆实现
选用最小堆的可行性
最⼩堆的性质有:
1. 是⼀颗完全⼆叉树;
2. 某⼀个节点的值总是⼩于等于它的⼦节点的值;
3. 堆中每个节点的⼦树都是最⼩堆;
应用到定时器的场景,我们只要按照每个任务触发的时间来对每一个堆节点(定时任务)进行组织,即可满足定时任务的顺序触发。
增加定时器即可通过在堆的尾部插入新的节点,时间复杂度插入为O(1),但是还得排序,故为O(logn)。
删除节点即可通过删除堆根节点,具体的删除操作为用最后一个定时器节点替换根节点,仍需要排序,时间复杂度为O(logn)。
最小堆相关结构设计
1.定义定时器结构
typedef void (*TimerHandler) (struct TimerNode *node);
// 定时器结构体
struct TimerNode {
int idx = 0; // 定时器在动态数组中的下标
int id = 0; // 定时器id
unsigned int expire = 0; // 定时器触发时间戳
TimerHandler cb = NULL; // 定时触发执行函数,回调函数
};
2.定义最小堆类
// 最小堆类---组织定时器
class MinHeapTimer {
public:
// 无参构造
MinHeapTimer() {
_heap.clear();
_map.clear();
}
// 内联函数,计算定时器个数
static inline int Count() {
return ++_count;
}
// 添加定时器,参数为定时器触发的时间间隔,以及回调函数
int AddTimer(uint32_t expire, TimerHandler cb);
// 根据定时器id删除定时器
bool DelTimer(int id);
// 获取定时器的触发时间戳
void ExpireTimer();
private:
// 内联函数,用于比较堆数组中两个定时器的(时间戳)大小
inline bool _lessThan(int lhs, int rhs) {
return _heap[lhs]->expire < _heap[rhs]->expire;
}
// 下沉
bool _shiftDown(int pos);
// 上浮
void _shiftUp(int pos);
// 删除定时器
void _delNode(TimerNode *node);
private:
vector<TimerNode*> _heap; // 动态数组存储定时器
map<int, TimerNode*> _map; // id映射定时器,快速判断定时器是否存在
static int _count; // 静态变量记录整个最小堆的定时器数量
};
int MinHeapTimer::_count = 0; // 初始化静态变量
3.代码实现
int MinHeapTimer::AddTimer(uint32_t expire, TimerHandler cb) {
int64_t timeout = current_time() + expire;
TimerNode* node = new TimerNode;
int id = Count();
node->id = id;
node->expire = timeout;
node->cb = cb;
node->idx = (int)_heap.size();
_heap.push_back(node);
_shiftUp((int)_heap.size() - 1);
_map.insert(make_pair(id, node));
return id;
}
bool MinHeapTimer::DelTimer(int id)
{
auto iter = _map.find(id);
if (iter == _map.end())
return false;
_delNode(iter->second);
return true;
}
void MinHeapTimer::_delNode(TimerNode *node)
{
int last = (int)_heap.size() - 1;
int idx = node->idx;
if (idx != last) {
std::swap(_heap[idx], _heap[last]);
_heap[idx]->idx = idx;
if (!_shiftDown(idx)) {
_shiftUp(idx);
}
}
_heap.pop_back();
_map.erase(node->id);
delete node;
}
void MinHeapTimer::ExpireTimer()
{
if (_heap.empty()) return;
uint32_t now = current_time();
do {
TimerNode* node = _heap.front();
if (now < node->expire)
break;
for (int i = 0; i < _heap.size(); i++)
std::cout << "touch idx: " << _heap[i] ->idx
<< " id: " << _heap[i]->id << " expire: "
<< _heap[i]->expire << std::endl;
if (node->cb) {
node->cb(node);
}
_delNode(node);
} while(!_heap.empty());
}
bool MinHeapTimer::_shiftDown(int pos){
int last = (int)_heap.size()-1;
int idx = pos;
for (;;) {
int left = 2 * idx + 1;
if ((left >= last) || (left < 0)) {
break;
}
int min = left; // left child
int right = left + 1;
if (right < last && !_lessThan(left, right)) {
min = right; // right child
}
if (!_lessThan(min, idx)) {
break;
}
std::swap(_heap[idx], _heap[min]);
_heap[idx]->idx = idx;
_heap[min]->idx = min;
idx = min;
}
return idx > pos;
}
void MinHeapTimer::_shiftUp(int pos)
{
for (;;) {
int parent = (pos - 1) / 2; // parent node
if (parent == pos || !_lessThan(pos, parent)) {
break;
}
std::swap(_heap[parent], _heap[pos]);
_heap[parent]->idx = parent;
_heap[pos]->idx = pos;
pos = parent;
}
}