time_wheel.h
#include <memory>
#include <list>
#include <vector>
#include <mutex>
#include <thread>
typedef struct TimePos{
int pos_ms;
int pos_sec;
int pos_min;
}TimePos_t;
typedef struct Event {
int id;
void(*cb)(void);
void* arg;
TimePos_t timePos;
int interval;
bool is_repeat;
}Event_t;
class TimeWheel {
typedef std::shared_ptr<TimeWheel> TimeWheelPtr;
typedef void (*EventCallback_t)(void );
typedef std::vector<std::list<Event_t>> EventSlotList_t;
public:
TimeWheel();
~TimeWheel();
void initTimeWheel(int steps, int maxMin);
int createTimingEvent(int interval, EventCallback_t callback, bool is_repeat = false);
/**
* @brief 取消定时任务
*
* @param eventId 任务ID
*/
void removeTimingEvent(int eventId);
/**
* @brief 打印现有任务ID
*/
void printEvents();
// 停止所有任务
void stop();
// 启动定时器
bool start();
private:
void loopForInterval();
int getCurrentMs(TimePos_t timePos);
int createEventId();
int processEvent(std::list<Event_t> &eventList);
void getTriggerTimeFromInterval(int interval, TimePos_t &timePos);
void insertEventToSlot(int interval, Event_t& event);
EventSlotList_t m_eventSlotList;
TimePos_t m_timePos;
std::unique_ptr<std::thread> m_loopThread = nullptr;
int m_firstLevelCount;
int m_secondLevelCount;
int m_thirdLevelCount;
int m_steps;
int m_increaseId; // not used
std::mutex m_mutex;
bool m_isRunning;
};
time_wheel.cpp
#include "time_wheel.h"
#include "color_log.hpp"
#include <iostream>
#include <memory.h>
#include <chrono>
#include <algorithm>
#include <functional>
TimeWheel::TimeWheel() : m_steps(0), m_firstLevelCount(0), m_secondLevelCount(60), m_thirdLevelCount(0),
m_increaseId (1), m_isRunning(false){
memset(&m_timePos, 0, sizeof(m_timePos));
}
TimeWheel::~TimeWheel() {
m_isRunning = false;
if (m_loopThread && m_loopThread->joinable()) {
m_loopThread->join();
}
}
void TimeWheel::loopForInterval() {
log_debug("start TimeWheel loop !!!");
while(m_isRunning) {
std::this_thread::sleep_for(std::chrono::milliseconds(m_steps));
// printf("唤醒\n");
TimePos pos = {0};
TimePos m_lastTimePos = m_timePos;
// 更新当前 TimeWheel 的槽
getTriggerTimeFromInterval(m_steps, pos);
m_timePos = pos;
{
std::unique_lock<std::mutex> lock(m_mutex);
// 如果分钟改变,在整点(分钟)处理
if (pos.pos_min != m_lastTimePos.pos_min)
{
// printf("分钟改变\n");
log_info("分钟改变 m_eventSlotList : [%d]", m_timePos.pos_min + m_firstLevelCount + m_secondLevelCount);
std::list<Event_t>* eventList = &m_eventSlotList[m_timePos.pos_min + m_firstLevelCount + m_secondLevelCount];
processEvent(*eventList);
eventList->clear();
}
else if (pos.pos_sec != m_lastTimePos.pos_sec)
{
// 在同一分钟内,但秒数改变,现在是在这个整点秒
// printf("秒改变\n");
// log_info("秒改变 m_eventSlotList : [%d]", m_timePos.pos_sec + m_firstLevelCount);
std::list<Event_t>* eventList = &m_eventSlotList[m_timePos.pos_sec + m_firstLevelCount];
processEvent(*eventList);
eventList->clear();
}
else if (pos.pos_ms != m_lastTimePos.pos_ms)
{
// 现在是在这个毫秒
// printf("毫秒改变\n");
// log_info("毫秒改变 m_eventSlotList : [%d]", m_timePos.pos_ms );
std::list<Event_t>* eventList = &m_eventSlotList[m_timePos.pos_ms];
processEvent(*eventList);
eventList->clear();
}
// printf("循环结束\n");
}
}
log_debug("exit TimeWheel loop !!!");
}
// 初始化 TimeWheel 的步长和最大分钟数,这决定了该轮的最大周期
void TimeWheel::initTimeWheel(int steps, int maxMin)
{
if (1000 % steps != 0){
log_info("无效的步长");
return;
}
m_steps = steps;
m_firstLevelCount = 1000/steps;
m_thirdLevelCount = maxMin;
log_info("m_firstLevelCount : [%d] m_secondLevelCount : [%d] m_thirdLevelCount : [%d]",
m_firstLevelCount, m_secondLevelCount, m_thirdLevelCount);
m_eventSlotList.resize(m_firstLevelCount + m_secondLevelCount + m_thirdLevelCount);
log_info("m_eventSlotList size : [%ld]", m_eventSlotList.size());
}
bool TimeWheel::start() {
if(m_isRunning) {
log_warn("定时器已经再运行了!!!!");
return true;
}
m_isRunning = true;
m_loopThread = std::make_unique<std::thread>(std::bind(&TimeWheel::loopForInterval, this));
if (!m_loopThread->joinable()) {
log_info("创建线程错误:%s", strerror(errno));
return false;
}
return true;
}
void TimeWheel::stop() {
m_isRunning = false;
m_eventSlotList.clear();
if (m_loopThread->joinable()) {
m_loopThread->join();
}
}
// 创建定时事件,参数 interval 表示触发间隔,callback 为回调函数
int TimeWheel::createTimingEvent(int interval, EventCallback_t callback, bool is_repeat){
if(interval < m_steps || interval % m_steps != 0 || interval >= m_steps*m_firstLevelCount*m_secondLevelCount*m_thirdLevelCount){
log_info("无效的间隔");
return 0;
}
log_info("开始创建事件");
Event_t event = {0};
event.interval = interval;
event.is_repeat = is_repeat;
event.cb = callback;
// 设置时间起点
event.timePos.pos_min = m_timePos.pos_min;
event.timePos.pos_sec = m_timePos.pos_sec;
event.timePos.pos_ms = m_timePos.pos_ms;
event.id = createEventId();
// 插入到 TimeWheel 的一个槽中
std::unique_lock<std::mutex> lock(m_mutex);
insertEventToSlot(interval, event);
log_info("创建完成");
return event.id;
}
int TimeWheel::createEventId() {
return m_increaseId++;
}
void TimeWheel::getTriggerTimeFromInterval(int interval, TimePos_t &timePos) {
// 获取当前时间:毫秒
int curTime = getCurrentMs(m_timePos);
// printf("间隔 = %d,当前毫秒 = %d\n", interval, curTime);
// 计算该间隔应该属于哪个槽
int futureTime = curTime + interval;
// printf("未来毫秒 = %d\n", futureTime);
timePos.pos_min = (futureTime/1000/60)%m_thirdLevelCount;
timePos.pos_sec = (futureTime%(1000*60))/1000;
timePos.pos_ms = (futureTime%1000)/m_steps;
// printf("下一分钟位置=%d,秒位置=%d,毫秒位置=%d\n", timePos.pos_min, timePos.pos_sec, timePos.pos_ms);
return;
}
int TimeWheel::getCurrentMs(TimePos_t timePos) {
return m_steps * timePos.pos_ms + timePos.pos_sec*1000 + timePos.pos_min*60*1000;
}
int TimeWheel::processEvent(std::list<Event_t> &eventList){
// printf("eventList.size=%d\n", eventList.size());
// 处理当前槽的事件
for(auto event = eventList.begin(); event != eventList.end(); event ++) {
// 计算当前毫秒
int currentMs = getCurrentMs(m_timePos);
// 计算该事件上次处理的时间(毫秒)
int lastProcessedMs = getCurrentMs(event->timePos);
// 计算现在和上次时间的距离(毫秒)
// int distanceMs = (currentMs - lastProcessedMs + (m_secondLevelCount+1)*60*1000)%((m_secondLevelCount+1)*60*1000);
// 考虑完成一轮时间轮的情况,计算时间距离
int distanceMs = (currentMs >= lastProcessedMs) ?
(currentMs - lastProcessedMs) :
(m_steps * m_firstLevelCount * m_secondLevelCount * m_thirdLevelCount - lastProcessedMs + currentMs);
// 如果 interval 等于 distanceMs,则需要处理该事件
if (event->interval == distanceMs) {
// 处理事件
event->cb(); //这里可以用线程池来工作,(自行在对象里加一个线程池)不然任务阻塞时间轮就不精准
// 获取当前位置作为该事件的起点
event->timePos = m_timePos;
if (event->is_repeat) {
// 是循环将该事件插入到槽中
insertEventToSlot(event->interval, *event);
} else {
log_info("event->id [%d] exit pool", event->id);
}
} else {
// 当处理整数点时触发这个条件
log_info("event->interval != distanceMs, ID [%d] , [%d] != [%d], [%d] ms after do", event->id, event->interval, distanceMs, event->interval - distanceMs);
// 虽然该事件在这个位置,但它没有到达时机,它将继续根据 distance ms 移动到下一个槽中进行计算。
insertEventToSlot((event->interval - distanceMs), *event);
}
}
return 0;
}
void TimeWheel::insertEventToSlot(int interval, Event_t& event)
{
// log_info("插入事件到槽");
TimePos_t timePos = {0};
// 计算该事件应该设置到哪个槽中
getTriggerTimeFromInterval(interval, timePos);
{
// printf("timePos.pos_min=%d, m_timePos.pos_min=%d\n", timePos.pos_min, m_timePos.pos_min);
// printf("timePos.pos_sec=%d, m_timePos.pos_sec=%d\n", timePos.pos_sec, m_timePos.pos_sec);
// printf("timePos.pos_ms=%d, m_timePos.pos_ms=%d\n", timePos.pos_ms, m_timePos.pos_ms);
// 如果分钟不等于当前分钟,首先将其插入到其分钟槽中
if (timePos.pos_min != m_timePos.pos_min)
{
// log_info("ID [%d] 插入到 %d 分钟槽", event.id, m_firstLevelCount + m_secondLevelCount + timePos.pos_min);
m_eventSlotList[m_firstLevelCount + m_secondLevelCount + timePos.pos_min].emplace_back(event);
}
// 如果分钟相等,但秒数改变,将插入到该整点秒的槽中
else if (timePos.pos_sec != m_timePos.pos_sec)
{
// log_info("ID [%d] 插入到 %d 秒槽", event.id, m_firstLevelCount + timePos.pos_sec);
m_eventSlotList[m_firstLevelCount + timePos.pos_sec].emplace_back(event);
}
// 如果分钟和秒数相等,意味着该事件不会在整点触发,将其设置到毫秒槽中
else if (timePos.pos_ms != m_timePos.pos_ms)
{
// log_info("ID [%d] 插入到 %d 毫秒槽", event.id, timePos.pos_ms);
m_eventSlotList[timePos.pos_ms].emplace_back(event);
}
}
return;
}
/**
* @brief 取消定时任务
*
* @param eventId 任务ID
*/
void TimeWheel::removeTimingEvent(int eventId) {
for (auto& eventList : m_eventSlotList) {
eventList.remove_if([eventId](const Event_t& event) {
if (event.id == eventId) {
log_info("remove Event ID: %d", event.id);
return true;
}
return false;
});
}
}
/**
* @brief 打印现有任务ID
*/
void TimeWheel::printEvents() {
bool events_is_empty = true;
for (const auto& eventList : m_eventSlotList) {
for (const auto& event : eventList) {
log_info("Event ID: %d", event.id);
events_is_empty = false;
}
}
if (events_is_empty) {
log_info("The timer has no events to work on");
}
}
time_wheel_main.cpp
#include <iostream>
#include <thread>
#include "time_wheel.h"
#include "color_log.hpp"
using namespace std;
void funccc(void) {
static int count = 1;
log_warn("exec 20s function [%d]",count++);
}
void funccc1(void) {
static int count = 1;
log_error("exec 40s function [%d]",count++);
}
int main()
{
TimeWheel wheel;
wheel.initTimeWheel(50, 2); //初始化tick为50ms,最大轮训时间为2分钟
wheel.start();
int task_id1 = wheel.createTimingEvent(20 * 1000, funccc, true);
log_debug("task_id1 : %d", task_id1);
int task_id2 = wheel.createTimingEvent(40 * 1000, funccc1, true);
log_debug("task_id1 : %d", task_id2);
int task_id3 = wheel.createTimingEvent(500, [](){
static int count = 1;
// log_debug("exec 500ms function [%d]",count++);
}, true);
log_debug("task_id1 : %d", task_id3);
int task_id4 = wheel.createTimingEvent(60 * 1000, [](){
static int count = 1;
log_fatal("exec 60s function [%d]",count++);
}, true);
log_debug("task_id1 : %d", task_id4);
int task_id5 = wheel.createTimingEvent(90 * 1000, [](){
static int count = 1;
log_fatal("exec 90s function [%d]",count++);
}, true);
log_debug("task_id1 : %d", task_id5);
int task_id6 = wheel.createTimingEvent(70 * 1000, [](){
static int count = 1;
log_fatal("exec 70s function [%d]",count++);
}, true);
log_debug("task_id1 : %d", task_id6);
int task_id7 = wheel.createTimingEvent(35 * 1000, [](){
static int count = 1;
log_fatal("exec 35s function [%d]",count++);
}, true);
log_debug("task_id1 : %d", task_id7);
// wheel.printEvents();
// std::this_thread::sleep_for(std::chrono::seconds(1));
// wheel.removeTimingEvent(task_id3);
// wheel.printEvents();
// std::this_thread::sleep_for(std::chrono::seconds(2));
// wheel.removeTimingEvent(task_id2);
// wheel.printEvents();
// std::this_thread::sleep_for(std::chrono::seconds(3));
// wheel.removeTimingEvent(task_id1);
// wheel.printEvents();
// wheel.stop();
while (1)
{
std::this_thread::sleep_for(std::chrono::milliseconds(10*1000));
}
}
运行结果

注意:如果需要保证定时器精准,需要自行修改代码,需在调用处理事件时event->cb(); 这里可以用线程池来工作,(自行在对象里加一个线程池)不然任务阻塞时间轮就不精准。

244

被折叠的 条评论
为什么被折叠?



