基于最小堆实现的定时器(HeapTimer)

思路:

实现Reactor模式的服务器时,通常需要和非阻塞的IO函数相结合,此外还存在保持与客户之间的连接等定时任务,所以如何将这些定时任务有效地组织起来十分重要。

本文实现了一个基于最小堆的定时器,插入O(logn)、删除O(1)、查找O(1),效率较高。

定时任务的结构

typedef std::function<void()> TimeoutCallBack;
typedef std::chrono::high_resolution_clock Clock;
typedef std::chrono::milliseconds MS;
typedef Clock::time_point TimeStamp;

struct TimerNode
{
	int id;//定时任务标识
	TimeStamp expires;//超时时间
	TimeoutCallBack func;//超时回调函数
	bool operator<(const TimerNode& other)
	{
		return this->expires < other.expires;
	}
};

定时器的结构

class HeapTimer
{
public:
	HeapTimer() { heap.reserve(64); }
	~HeapTimer() 
    { 
        heap.clear();
        ref.clear(); 
    }
private:
	std::vector<TimerNode> heap;//最小堆
	std::unordered_map<int, size_t> ref;//ref[定时任务标识]=最小堆内的下标
};

主要的成员函数:

(1)上浮        void HeapTimer::swim(size_t i)

void HeapTimer::swim(size_t i)//上浮
{
	assert(i>=0&&i<heap.size());
    while (i > 0)
    {
        size_t parent = (i - 1) / 2;
        if (heap[parent] < heap[i])
            break;
        swapNode(i, parent);
        i = parent;
    }
}

(2)下沉        bool HeapTimer::sink(size_t index,size_t n)

bool HeapTimer::sink(size_t index,size_t n)//下沉
{
    assert(index >= 0 && index < heap.size());
    assert(n >= 0 && n <= heap.size());
    size_t i = index;
    while (i * 2 + 1<n)
    {
        size_t left = i * 2 + 1;
        size_t right = i * 2 + 2;
        size_t older = left;
        if (right<n && heap[right]<heap[older])
        {
            older = right;
        }
        if (heap[i]<heap[older])
            break;
        swapNode(i, older);
        i = older;
    }
    return i > index;
}

 (3)交换       void HeapTimer::swapNode(size_t i, size_t j)

这里直接选择STL通用的swap函数,std::swap(i, j)默认采用深拷贝,所以如果交换的两个数据结构内含有指针成员,应该自己定制swap函数,交换指针即可,无需进行默认的深拷贝动作。

但是,定时任务TimerNode内并无指针成员,默认的swap函数效率已足够高。

void HeapTimer::swapNode(size_t i, size_t j)
{
    assert(i >= 0 && i < heap.size());
    assert(j >= 0 && j < heap.size());
    std::swap(heap[i], heap[j]);
    ref[heap[i].id] = i;
    ref[heap[j].id] = j;
}

(4)添加定时任务        void HeapTimer::add(int id, int timeout, const TimeoutCallBack& func)

void HeapTimer::add(int id, int timeout, const TimeoutCallBack& func)
{
    assert(id >= 0);
    size_t i;
    if (ref.count(id) == 0)
    {
        i = heap.size();
        ref[id] = i;//向堆底的最后一个位置加入新的定时任务
        heap.push_back({ id,Clock::now() + MS(timeout),func });
        swim(i);//调整堆结构(上浮)
    }
    else//若已存在,则更新超时时间
    {
        i = ref[id];
        heap[i].expires = Clock::now() + MS(timeout);
        heap[i].func = func;
        if (!sink(i, heap.size()))
        {
            swim(i);
        }
    }
}

(5)删除指定任务

void HeapTimer::del(size_t i)                //删除指定下标的定时任务

void HeapTimer::doWork(int id)            //删除指定标识的定时任务

void HeapTimer::del(size_t i)//删除指定下标的定时任务
{
    assert(!heap.empty() && i >= 0 && i < heap.size());
    size_t n = heap.size() - 1;
    if (i < n)
    {
        swapNode(i, n);//与堆底的最后一个元素交换位置
        if (!sink(i, n))//调整堆结构
        {
            swim(i);
        }
    }
    ref.erase(heap.back().id);
    heap.pop_back();
}

void HeapTimer::doWork(int id)//删除指定标识的定时任务
{
    if (heap.empty() || ref.count(id) == 0)
    {
        return;
    }
    size_t i = ref[id];
    TimerNode cur = heap[i];
    cur.func();
    del(i);
}

 (6)清除超时节点        void HeapTimer::tick()

void HeapTimer::tick()//清除超时节点
{
    if (heap.empty())
    {
        return;
    }
    while (!heap.empty())
    {
        TimerNode cur = heap.front();
        if (std::chrono::duration_cast<MS>(cur.expires - Clock::now()).count() > 0)
        {
            break;
        }
        cur.func();
        pop();
    }
}

void HeapTimer::pop()//清除堆顶节点
{
    assert(!heap.empty());
    del(0);
}

(7)心搏函数        int HeapTimer::getNextTick()

设计思路:将所有定时任务中超时时间最小(最快到期)的一个定时任务的超时值作为心搏间隔,这样一旦下一次调用心搏函数时,tick()函数被调用,此时这个超时时间最小的定时任务必然到期,就在tick()函数中处理这个超时任务。然后,再次从剩余的任务中找出一个超时时间最小的,将这段时间设置为下一次心搏间隔,如此不断反复。

int HeapTimer::getNextTick()//得到堆顶节点剩余的阻塞时间(心搏函数)
{
    tick();
    size_t res = -1;
    if (!heap.empty())
    {
        res = std::chrono::duration_cast<MS>(heap.front().expires - Clock::now()).count();
        if (res < 0)
            res = 0;
    }
    return res;
}

 源码:

heaptimer.h

#ifndef HEAP_TIMER_H
#define HEAP_TIMER_H

#include<functional>
#include<chrono>
#include<assert.h>
#include<unordered_map>
#include<vector>

typedef std::function<void()> TimeoutCallBack;
typedef std::chrono::high_resolution_clock Clock;
typedef std::chrono::milliseconds MS;
typedef Clock::time_point TimeStamp;

struct TimerNode
{
	int id;
	TimeStamp expires;
	TimeoutCallBack func;
	bool operator<(const TimerNode& other)
	{
		return this->expires < other.expires;
	}
};

//基于最小堆的定时器
class HeapTimer
{
public:
	HeapTimer() { heap.reserve(64); }

	~HeapTimer() { clear(); }

	void add(int id, int timeOut, const TimeoutCallBack& func);

	void adjust(int id, int newExpires);

	void doWork(int id);

	void tick();

	void pop();
	
	void clear();
	
	int getNextTick();

private:
	std::vector<TimerNode> heap;

	std::unordered_map<int, size_t> ref;

	void swim(size_t i);

	bool sink(size_t index, size_t n);

	void del(size_t i);

	void swapNode(size_t i, size_t j);
};

#endif // !HEAP_TIMER_H

heaptimer.cpp 

#include "heaptimer.h"

void HeapTimer::swim(size_t i)//上浮
{
	assert(i>=0&&i<heap.size());
    while (i > 0)
    {
        size_t parent = (i - 1) / 2;
        if (heap[parent] < heap[i])
            break;
        swapNode(i, parent);
        i = parent;
    }
}

void HeapTimer::swapNode(size_t i, size_t j)
{
    assert(i >= 0 && i < heap.size());
    assert(j >= 0 && j < heap.size());
    std::swap(heap[i], heap[j]);
    ref[heap[i].id] = i;
    ref[heap[j].id] = j;
}

bool HeapTimer::sink(size_t index,size_t n)//下沉
{
    assert(index >= 0 && index < heap.size());
    assert(n >= 0 && n <= heap.size());
    size_t i = index;
    while (i * 2 + 1<n)
    {
        size_t left = i * 2 + 1;
        size_t right = i * 2 + 2;
        size_t older = left;
        if (right<n && heap[right]<heap[older])
        {
            older = right;
        }
        if (heap[i]<heap[older])
            break;
        swapNode(i, older);
        i = older;
    }
    return i > index;
}

void HeapTimer::add(int id, int timeout, const TimeoutCallBack& func)//添加新的时间节点
{
    assert(id >= 0);
    size_t i;
    if (ref.count(id) == 0)
    {
        i = heap.size();
        ref[id] = i;
        heap.push_back({ id,Clock::now() + MS(timeout),func });
        swim(i);
    }
    else//若已存在,则更新阻塞时间
    {
        i = ref[id];
        heap[i].expires = Clock::now() + MS(timeout);
        heap[i].func = func;
        if (!sink(i, heap.size()))
        {
            swim(i);
        }
    }
}

void HeapTimer::del(size_t i)//删除指定位置的时间节点
{
    assert(!heap.empty() && i >= 0 && i < heap.size());
    size_t n = heap.size() - 1;
    if (i < n)
    {
        swapNode(i, n);
        if (!sink(i, n))
        {
            swim(i);
        }
    }
    ref.erase(heap.back().id);
    heap.pop_back();
}

void HeapTimer::doWork(int id)//删除指定序号的时间节点
{
    if (heap.empty() || ref.count(id) == 0)
    {
        return;
    }
    size_t i = ref[id];
    TimerNode cur = heap[i];
    cur.func();
    del(i);
}

void HeapTimer::adjust(int id, int timeout)//更新阻塞时间
{
    assert(!heap.empty() && ref.count(id) > 0);
    heap[ref[id]].expires = Clock::now() + MS(timeout);
    sink(ref[id], heap.size());
}

void HeapTimer::tick()//清除超时节点
{
    if (heap.empty())
    {
        return;
    }
    while (!heap.empty())
    {
        TimerNode cur = heap.front();
        if (std::chrono::duration_cast<MS>(cur.expires - Clock::now()).count() > 0)
        {
            break;
        }
        cur.func();
        pop();
    }
}

void HeapTimer::pop()//删除堆顶节点
{
    assert(!heap.empty());
    del(0);
}

void HeapTimer::clear()
{
    ref.clear();
    heap.clear();
}

int HeapTimer::getNextTick()//得到堆顶节点剩余的阻塞时间(心搏函数)
{
    tick();
    size_t res = -1;
    if (!heap.empty())
    {
        res = std::chrono::duration_cast<MS>(heap.front().expires - Clock::now()).count();
        if (res < 0)
            res = 0;
    }
    return res;
}
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值