时间轮算法设计实现

大耗子 2021年01月25日 46次浏览

时间轮算法基本思想

  • 参照时钟运转的思路,每个相同刻度下的时间是相同的,将所有相同时间的时间放在一个槽中,通过用一个指针每过一个刻度指向时间轮的槽,取出槽中链表,循环遍历相同刻度下的事件。(liunx内核就是这么做的)。
    时间轮基本结构
  • 如图所示的是一个时间轮的基本结构。时间轮分为N个时间槽slot,每时间槽指向一个定时器链表,这个链表里包含多个定时器,这个链表的定时器Timeout时间相同,因此链表里的定时器无须排序。

简单时间轮的实现

#include <unistd.h>
#include <iostream>
#include <vector>
#include <list>
using namespace std;

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

class CTimerNode {
public:
    CTimerNode(int fd) : id(fd), ref(0) {}

	// 由于在时间轮中,如果数量过多,查找麻烦,因此要离线直接置为引用为0即可.
    void Offline() {
        this->ref = 0;
    }

    bool TryKill() {
        DecrRef();
        if (this->ref == 0) {
            cout << id << " is killed down" << endl;
            return true;
        }
        cout << id << " ref is " << ref << endl;
        return false;
    }
	// 通过增加引用计数,避免了删除节点的时候去查找
	// 只要引用计数为0,则说明该节点需要删除
    void IncrRef() {
        this->ref++;
    }

protected:
    void DecrRef() {
        this->ref--;
    }

private:
    int ref;
    int id;
};

const int TW_SIZE = 16;
const int EXPIRE = 10;
const int TW_MASK = TW_SIZE - 1;
static size_t iRealTick = 0;
typedef list<CTimerNode*> TimeList;
typedef TimeList::iterator TimeListIter;
typedef vector<TimeList> TimeWheel;

void AddTimeout(TimeWheel &tw, CTimerNode *p) {
    if (p) {
        p->IncrRef();
		// 当前事件加上要EXPIRE秒后要执行
        TimeList &le = tw[(iRealTick+EXPIRE) & TW_MASK];
        le.push_back(p);
    }
}

// 用来表示delay时间后调用 AddTimeout
void AddTimeoutDelay(TimeWheel &tw, CTimerNode *p, size_t delay) {
    if (p) {
        p->IncrRef();
        TimeList &le = tw[(iRealTick+EXPIRE+delay) & TW_MASK];
        le.push_back(p);
    }
}

// 每过一个时间刻度,执行一次
void TimerShift(TimeWheel &tw)
{
    size_t tick = iRealTick;
    iRealTick++;
    TimeList &le = tw[tick & TW_MASK];
    TimeListIter iter = le.begin();
    for (; iter != le.end();) {
		// 取出结点
        CTimerNode *p = *iter;
        if (p && p->TryKill()) {
            delete p;
            le.erase(iter++);
        } else {
			// 此处执行定时任务即可
			// 定时任务可以绑定再timenode结点中。
			iter++;
		}
    }
}

static time_t
current_time() {
	time_t t;
#if !defined(__APPLE__) || defined(AVAILABLE_MAC_OS_X_VERSION_10_12_AND_LATER)
	struct timespec ti;
	clock_gettime(CLOCK_MONOTONIC, &ti);
	t = (time_t)ti.tv_sec;
#else
	struct timeval tv;
	gettimeofday(&tv, NULL);
	t = (time_t)tv.tv_sec;
#endif
	return t;
}

int main ()
{
    TimeWheel tw(TW_SIZE);

    CTimerNode *p = new CTimerNode(10001);

    AddTimeout(tw, p);

    AddTimeoutDelay(tw, p, 5);

    time_t start = current_time();
    for (;;) {
        time_t now = current_time();
        if (now - start > 0) {
            for (int i=0; i<now-start; i++)
                TimerShift(tw);
            start = now;
            cout << "check timer shift " << iRealTick <<  endl;
        }
        usleep(2500);
    }

    return 0;
}

复杂时间轮(linux内核时间轮的思路)

时间轮

  • 只要低层的指针刻度走完,就将高一层的指针刻度前进一格,并将那一格的数据释放到低一层的时间轮中。思想就和时钟的时分秒三种刻度的运转差不多。只是这个时间轮,每个刻度里面带了一长串的链表。