前言
我们在网络编程中会遇到这样一个场景:我们需要维护大量的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;
}
测试结果如下:
测试一个分钟级别的:
以上就是时间轮定时器的全部内容,定时器类可直接使用,但需要根据业务逻辑做适当调整。