用时间轮实现定时器

前言

        我们在网络编程中会遇到这样一个场景:我们需要维护大量的TCP连接以进行网络通信,但这些连接中有大量的空闲。我们如何高效地管理这些连接?答案是定时器,我们给每个连接设置定时器来自动管理,规定时间内未进行通信则自动关闭连接。

        但是你应该能很快发现问题,连接数量一多,这些与连接挂钩的定时器也多了,系统的整体性能就会急剧下降,这时候就要请出今天的主角时间轮了。

原理

       时间轮(Timing Wheel)是一种高效的定时器数据结构,广泛用于网络编程、事件驱动编程和操作系统中,用于管理和调度在特定时间点需要执行的任务。它的核心思想是使用一个循环数组(或称为轮子)来存储定时事件,并通过一个指针(或称为“时钟指针”)来跟踪当前时间。

        我们先从时钟入手,我们知道时钟由时针、分针、秒针三部分组成,秒针一圈分针一格,分针一圈时针一格。现在我们将这三个针分开,每个针转一圈都会回到原点。

        我们将其抽象简化成三个数组,把任务按照执行序依次以链表的方式挂在各个槽位,比如有一个36秒的任务,我们就可以计算出它会挂在秒针的第36个槽位上,当秒针指向这个槽位时就会执行这个槽位的任务。这时候你可能疑惑,如果是挂在分针上的任务,那执行逻辑又该如何?这时候就要小心了,当分针走到指定槽位,就需要将槽位上挂载的所有任务重新映射到秒针上,时针也是如此,这样一来,真正的执行者只有秒针数组。

实现思路

       接下来我们一个使用时间轮的定时器。首先来看任务节点,由于我们需要对分针,时针的槽位上的任务重新分配,自然过期时间是必不可少的,回调函数、参数也必不可少,此外,重新分配的机制使节点位置一直在改变致使我们无法删除节点,所以我们添加一个执行字段,等到这个任务启动时通过这个字段判断是否执行此任务。

// 定义定时器节点
class TimerNode {
public:
    std::function<void(TimerNode*)> callback; // 定时器回调函数
    uint32_t expire;                           // 定时器过期时间
    bool cancel;                               // 是否取消定时器
    int id;                                    // 携带参数
};

        节点问题讨论完了,重头戏来了,我们怎么设计时间轮类。前文我们分析了时间轮的由来,既然执行任务都在秒针那一层,分针与时针都需要到点重新分配,我们何不干脆使用两个轮?一个用来执行,一个用于存储。也就是说,我们可以设计一个近程定时器和一个远程定时器。我们将远程定时器分层级来模拟分针和时针,不局限于2个。此外,我们还需要几个成员变量存放当前时间、当前节点,以及一个互斥锁供多线程访问。

        我们还需要几个成员函数:添加任务节点、删除节点(设置执行字段为false)、处理到期的定时任务以及循环推进函数等等。总体设计及解释如下:

#include <cstdint>
#include <functional>
#include <list>
#include <mutex>
#include <vector>
#include <memory>
#include <chrono>
#include<iostream>
#include<thread>

#define TIME_NEAR_SHIFT 8
#define TIME_NEAR (1 << TIME_NEAR_SHIFT) // 近程任务
#define TIME_LEVEL_SHIFT 6
#define TIME_LEVEL (1 << TIME_LEVEL_SHIFT) // 远程任务
#define TIME_NEAR_MASK (TIME_NEAR - 1)
#define TIME_LEVEL_MASK (TIME_LEVEL - 1)

struct TimerNode {
    std::shared_ptr<TimerNode> next; // 使用智能指针管理内存
    uint32_t expire; // 过期时间
    std::function<void()> callback; // 回调函数
    uint8_t cancel; // 取消标志
    int id;
};

class TimerWheel {
private:
    struct List {
        std::shared_ptr<TimerNode> head;
        std::shared_ptr<TimerNode> tail;
    };

    List near[TIME_NEAR]; // 近程任务链表
    std::vector<std::vector<List>> t{4, std::vector<List>(TIME_LEVEL)}; // 远程任务分四层链表
    std::mutex mutex_;
    uint32_t time_; // 当前时间
    uint64_t current_point_; // 最后检查时间

public:
    // 构造函数
    TimerWheel() : time_(0),current_point_(0) {
        // 初始化近程和远程任务链表
        for (auto& list : near) {
            list.head = std::make_shared<TimerNode>();
            list.tail = list.head;
        }
        for (auto& level : t) {
            for (auto& list : level) {
                list.head = std::make_shared<TimerNode>();
                list.tail = list.head;
            }
        }
    }

    // 析构函数
    ~TimerWheel() {
        clear_timer();
    }

   std::shared_ptr<TimerNode> link_clear(List *list) {
        std::shared_ptr<TimerNode> ret = list->head->next;
        list->head->next.reset();
        list->tail = list->head;
        return ret;
    }

    void link(List *list, std::shared_ptr<TimerNode> node) {
        list->tail->next = node;
        list->tail = node;
        node->next=nullptr;
    }

    void add_node(std::shared_ptr<TimerNode> node) { // 添加节点
        uint32_t time = node->expire;
        uint32_t current_time = time_;
        uint32_t msec = time - current_time;
        if (msec < TIME_NEAR) { // 分层添加任务
            link(&near[time & TIME_NEAR_MASK], node);
        } else if (msec < (1 << (TIME_NEAR_SHIFT + TIME_LEVEL_SHIFT))) {
            link(&t[0][(time >> TIME_NEAR_SHIFT) & TIME_LEVEL_MASK], node);
        } else if (msec < (1 << (TIME_NEAR_SHIFT + 2 * TIME_LEVEL_SHIFT))) {
            link(&t[1][(time >> (TIME_NEAR_SHIFT + TIME_LEVEL_SHIFT)) & TIME_LEVEL_MASK], node);
        } else if (msec < (1 << (TIME_NEAR_SHIFT + 3 * TIME_LEVEL_SHIFT))) {
            link(&t[2][(time >> (TIME_NEAR_SHIFT + 2 * TIME_LEVEL_SHIFT)) & TIME_LEVEL_MASK], node);
        } else {
            link(&t[3][(time >> (TIME_NEAR_SHIFT + 3 * TIME_LEVEL_SHIFT)) & TIME_LEVEL_MASK], node);
        }
    }

    void move_list(int level, int idx) { // 清除链表并将所有定时器节点重新添加到时间轮中,以便重新分配。
        std::shared_ptr<TimerNode> current = link_clear(&t[level][idx]);
        while (current) {
            std::shared_ptr<TimerNode> temp = current->next;
            add_node(current);
            current = temp;
        }
    }

    void timer_shift() { // 更新时间轮的时间,并在必要时移动远程定时器链表中的定时器。
        int mask = TIME_NEAR;
        uint32_t ct = ++time_;
        if (ct == 0) {
            move_list(3, 0);
        } else {
            uint32_t time = ct >> TIME_NEAR_SHIFT;
            int i = 0;
            while ((ct & (mask - 1)) == 0) {
                int idx = time & TIME_LEVEL_MASK;
                if (idx != 0) { // 重新分配
                    move_list(i, idx);
                    break;
                }
                mask <<= TIME_LEVEL_SHIFT;
                time >>= TIME_LEVEL_SHIFT;
                ++i;
            }
        }
    }

    void dispatch_list(std::shared_ptr<TimerNode> current) { // 执行链表中的所有定时器节点,并释放它们。
       do {
		    std::shared_ptr<TimerNode> temp = current;
		    current=current->next;
            if (temp->cancel == 0)
                temp->callback();
	    } while (current);
    }

    void timer_execute() { // 执行当前时间点的所有近程定时器。
        int idx = time_ & TIME_NEAR_MASK;
	
	    while (near[idx].head->next) {
	    	std::shared_ptr<TimerNode> current = link_clear(&near[idx]);
	    	mutex_.unlock();
	    	dispatch_list(current);
	    	mutex_.lock();
	    }
    }

    void timer_update() { // 执行所有到期的定时器,并更新时间轮。
        std::lock_guard<std::mutex> lock(mutex_);
	    timer_execute();
	    timer_shift();
	    timer_execute();
    }

    void del_timer(std::shared_ptr<TimerNode> node) {
        node->cancel = 1;
    }

    static uint64_t gettime() { // 获取当前时间
        using namespace std::chrono;
        auto now = steady_clock::now().time_since_epoch();
        return duration_cast<milliseconds>(now).count();
    }

    void expire_timer() { // 检查是否有定时器已经过期。
         uint64_t cp = gettime();
        if (cp != current_point_) {
            uint32_t diff = static_cast<uint32_t>(cp - current_point_);
            current_point_ = cp;
            for (uint32_t i = 0; i < diff; ++i) {
                timer_update();
            }
        }
    }

    void clear_timer() {
       int i,j;
	    for (i=0;i<TIME_NEAR;i++) {
		    List * list = &this->near[i];
		    std::shared_ptr<TimerNode> current = list->head->next;
		    while(current) {
		    	std::shared_ptr<TimerNode> temp = current;
		    	current = current->next;
		    }
		    link_clear(&this->near[i]);
	    }
	    for (i=0;i<4;i++) {
	    	for (j=0;j<TIME_LEVEL;j++) {
	    		List * list = &this->t[i][j];
	    		std::shared_ptr<TimerNode> current = list->head->next;
	    		while (current) {
	    			std::shared_ptr<TimerNode> temp = current;
	    			current = current->next;
	    		}
	    		link_clear(&this->t[i][j]);
	    	}
	    }
    }


};

void testCallback(int id) {
    std::cout << "Timer " << id << " triggered" << std::endl;
}

int main() {
    TimerWheel wheel;

    // 添加一些定时任务
    for (int i = 0; i < 5; ++i) {
        int delay = (i + 1) * 1000; // 设置不同的延迟时间(毫秒)
        auto node = std::make_shared<TimerNode>();
        node->expire = TimerWheel::gettime() + delay;
        node->callback = std::bind(testCallback, i);
        node->cancel = 0;
        node->id = i;
        wheel.add_node(node);
        std::cout << "Timer " << i << " added with delay " << delay << "ms" << std::endl;
    }

    // 模拟定时器轮询
    std::thread timerThread([&wheel]() {
        while (true) {
            wheel.expire_timer();
            std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 每100ms检查一次
        }
    });

    // 主线程继续执行其他任务,例如等待用户输入以结束程序
    std::cout << "Press Enter to stop..." << std::endl;
    std::cin.get();

    // 等待定时器线程结束
    timerThread.join();

    return 0;
}

  测试结果如下:

测试一个分钟级别的:

以上就是时间轮定时器的全部内容,定时器类可直接使用,但需要根据业务逻辑做适当调整。

  • 29
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值