1. 为什么需要时间轮 (Timing Wheel)?
在处理大规模定时任务(如心跳检测、延迟重试)时,传统的 std::priority_queue 或 std::set 定时器存在明显的瓶颈:
*效率问题:插入和删除的时间复杂度为 O(logN)O(\log N)O(logN)。当任务达到万级时,频繁的堆调整会消耗大量 CPU。
*时间轮优势:通过槽位(Bucket)映射,插入、删除和执行的时间复杂度均为 O(1)O(1)O(1),类似于哈希表的原理,是高性能网络框架(如 Netty, Kafka)的首选。
2. 核心设计思路
时间轮本质上是一个循环数组,每个元素代表一个时间刻度(Tick)。
槽位(Slots):数组的每个位置存放一个任务链表。
指针(Cursor):随时间推移在数组上循环移动。
轮次数(Rotation):如果任务延迟超过一圈,则记录剩余圈数。
单例模式:使用 C++11 Meyers' Singleton 确保全局只有一个 DeviceTreeManager 管理硬件树的定时任务。
3. 基础版实现:易用的 Meyers 单例定时器
基础版适用于通用的 C++ 场景,逻辑简单,适合快速集成。
cpp
#include <iostream>
#include <vector>
#include <list>
#include <functional>
#include <mutex>
#include <memory>
// 定时器任务结构
struct TimerTask {
int rotation; // 需要转多少圈
std::function<void()> callback; // 任务回调
TimerTask(int r, std::function<void()> cb) : rotation(r), callback(cb) {}
};
class DeviceTreeManager {
public:
static DeviceTreeManager& getInstance() {
static DeviceTreeManager instance;
return instance;
}
// 添加定时器 (ms)
void addTimer(int timeout_ms, std::function<void()> cb) {
std::lock_guard<std::mutex> lock(mtx_);
int ticks = timeout_ms / tick_ms_;
int rotation = ticks / slot_count_;
int target_slot = (current_slot_ + ticks) % slot_count_;
wheel_[target_slot].emplace_back(std::make_shared<TimerTask>(rotation, cb));
}
// 时间轮拨动一格
void tick() {
std::lock_guard<std::mutex> lock(mtx_);
auto& tasks = wheel_[current_slot_];
for (auto it = tasks.begin(); it != tasks.end(); ) {
if ((*it)->rotation > 0) {
(*it)->rotation--;
it++;
} else {
(*it)->callback(); // 执行任务
it = tasks.erase(it);
}
}
current_slot_ = (current_slot_ + 1) % slot_count_;
}
private:
DeviceTreeManager() : slot_count_(60), tick_ms_(100), current_slot_(0) {
wheel_.resize(slot_count_);
}
int slot_count_; // 槽位总数
int tick_ms_; // 每格时间间隔
int current_slot_; // 当前指针位置
std::vector<std::list<std::shared_ptr<TimerTask>>> wheel_;
std::mutex mtx_;
};
4. 优化版实现:基于 Linux timerfd 的工业级方案
在 Linux 环境下,使用 usleep 或 sleep 驱动时间轮会导致严重的精度漂移。优化版引入了 Linux 原生的 timerfd,将定时器信号转化为文件描述符。
优化点:
*硬核触发:使用内核级 timerfd,消除用户态休眠带来的误差。
*事件驱动:可轻松集成进 epoll 异步模型。
*资源清理:在析构中安全关闭文件句柄。
cpp
#ifndef DEVICE_TREE_MANAGER_HPP
#define DEVICE_TREE_MANAGER_HPP
#include <iostream>
#include <vector>
#include <list>
#include <functional>
#include <memory>
#include <mutex>
#include <thread>
#include <atomic>
#include <sys/timerfd.h>
#include <unistd.h>
/**
* @brief 定时器任务结构体
*/
struct TimerTask {
int rotation; // 剩余旋转圈数
std::function<void()> callback; // 任务触发时的回调
TimerTask(int r, std::function<void()> cb) : rotation(r), callback(cb) {}
};
/**
* @brief 基于时间轮的设备管理器单例
*/
class DeviceTreeManager {
public:
// C++11 线程安全单例
static DeviceTreeManager& getInstance() {
static DeviceTreeManager instance;
return instance;
}
// 禁用拷贝与赋值
DeviceTreeManager(const DeviceTreeManager&) = delete;
DeviceTreeManager& operator=(const DeviceTreeManager&) = delete;
/**
* @brief 启动时间轮引擎
* @param tick_ms 拨动精度(毫秒)
*/
void start(int tick_ms = 100) {
if (running_.exchange(true)) return; // 防止重复启动
tick_ms_ = tick_ms;
engine_thread_ = std::thread(&DeviceTreeManager::runEngine, this);
}
/**
* @brief 停止时间轮引擎
*/
void stop() {
running_ = false;
if (engine_thread_.joinable()) {
engine_thread_.join();
}
}
/**
* @brief 注册一个定时任务
* @param timeout_ms 延迟执行的时间
* @param cb 回调函数
*/
void addTimer(int timeout_ms, std::function<void()> cb) {
std::lock_guard<std::mutex> lock(mtx_);
int ticks = timeout_ms / tick_ms_;
int rotation = ticks / slot_count_;
int target_slot = (current_slot_ + ticks) % slot_count_;
wheel_[target_slot].emplace_back(std::make_shared<TimerTask>(rotation, cb));
std::cout << "[Timer] Added task to slot " << target_slot << " with rotation " << rotation << std::endl;
}
private:
DeviceTreeManager() : slot_count_(60), tick_ms_(100), current_slot_(0), running_(false) {
wheel_.resize(slot_count_);
}
~DeviceTreeManager() {
stop();
}
// 时间轮核心引擎逻辑
void runEngine() {
// 1. 创建 Linux timerfd
int tfd = timerfd_create(CLOCK_MONOTONIC, 0);
if (tfd == -1) {
perror("timerfd_create");
return;
}
// 2. 设置定时器参数
struct itimerspec spec;
spec.it_interval.tv_sec = tick_ms_ / 1000;
spec.it_interval.tv_nsec = (tick_ms_ % 1000) * 1000000;
spec.it_value = spec.it_interval;
if (timerfd_settime(tfd, 0, &spec, NULL) == -1) {
perror("timerfd_settime");
close(tfd);
return;
}
uint64_t expirations;
while (running_) {
// 3. 阻塞等待内核信号(高性能,不占 CPU)
ssize_t s = read(tfd, &expirations, sizeof(expirations));
if (s != sizeof(expirations)) continue;
// 4. 拨动时间轮
tick();
}
close(tfd);
}
void tick() {
std::lock_guard<std::mutex> lock(mtx_);
auto& current_list = wheel_[current_slot_];
for (auto it = current_list.begin(); it != current_list.end(); ) {
if ((*it)->rotation > 0) {
(*it)->rotation--;
++it;
} else {
// 触发任务回调
if ((*it)->callback) (*it)->callback();
it = current_list.erase(it);
}
}
// 指针向前移动
current_slot_ = (current_slot_ + 1) % slot_count_;
}
// 成员变量
const int slot_count_;
int tick_ms_;
int current_slot_;
std::vector<std::list<std::shared_ptr<TimerTask>>> wheel_;
std::mutex mtx_;
std::thread engine_thread_;
std::atomic<bool> running_;
};
#endif
调用代码:
cpp
#include "DeviceTreeManager.hpp"
int main() {
auto& manager = DeviceTreeManager::getInstance();
// 1. 启动引擎(每格 100ms)
manager.start(100);
// 2. 注册不同时间的任务
manager.addTimer(500, []() {
std::cout << ">>> [Event] 0.5s task triggered!" << std::endl;
});
manager.addTimer(2000, []() {
std::cout << ">>> [Event] 2.0s task triggered!" << std::endl;
});
manager.addTimer(7000, []() {
std::cout << ">>> [Event] 7.0s task (multi-rotation) triggered!" << std::endl;
});
// 3. 模拟主程序运行
std::cout << "Main thread working... (Wait 10s to see all timers)" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(10));
// 4. 停止
manager.stop();
std::cout << "Engine stopped." << std::endl;
return 0;
}
*对于短时间、高频率的任务,时间轮的 O(1)O(1)O(1) 性能是碾压堆定时器的。
*在 Linux 系统中,永远优先考虑 timerfd,它是稳定性的保障。
*建议在生产环境中将回调函数放入线程池执行,避免任务过重导致时间轮"卡顿"。