当前位置: 首页 > news >正文

定时器的学习(二)

前言

之前实现过定时器,不过当时采用红黑树,使用C/C++中的set实现,看了下开源代码,发现使用multimap来实现,故写一点心得体会

一、定时器架构设计哲学

1.1 定时器的双重构成要素

定时器系统由 容器结构驱动方式 两个核心要素构成:

  • 容器结构:用于存储和管理定时任务的数据结构
  • 驱动方式:触发和执行定时任务的机制

1.2、主流定时器容器对比

容器类型数据结构时间复杂度适用场景
红黑树平衡二叉搜索树插入/删除: O(log n),获取最小: O(1)需要精确时间管理的复杂系统
最小堆完全二叉树插入/删除: O(log n),获取最小: O(1)简单定时任务,性能要求高
时间轮哈希表+链表插入/删除: O(1),触发: O(n)大量短时定时任务

1.3、红黑树在C++中的实现选择

结合之前的定时器实现,可知道,在C++中,STL提供了四个红黑树容器,分别是set,multiset,mapmultimap

  • map<key, value>: 以key存触发时间,value存任务,可以实现一个简单的定时器,当存在多个相同触发时间的任务时,map处理起来会比较麻烦。
  • set:可以以一个自定义结构作为key值进行存储,并且按触发时间进行排序,也可以处理多个相同触发时间的任务。
// 方案1:map - 简单但不完善
std::map<time_t, TimerTask> timers;
// 缺陷:相同触发时间的任务会被覆盖// 方案2:set + 自定义结构 - 完整但复杂
struct TimerNode {time_t expire;      // 过期时间uint64_t id;        // 唯一标识符(解决时间冲突)bool operator<(const TimerNode& other) const {return expire < other.expire || (expire == other.expire && id < other.id);}
};
std::set<TimerNode> timers;// 方案3:multimap - TrinityCore的选择
std::multimap<uint64_t, BasicEvent*> events;
// 优势:天然支持相同触发时间的多个任务
  • multimap<key, value>: 在之前认为使用multimap麻烦,不如使用set或者最小堆来实现定时器,但看了部分开源项目后,发现其实multimap是实现定时器的一个不错的选择。
  • multiset: 同样可以以一个自定义结构作为key值进行存储,并且按触发时间进行排序,也可以处理多个相同触发时间的任务。
    个人认为,mutilset和multimap的比较:
容器multimapmultiset
性能都是红黑树,持平持平
维护简单直观,易于维护不易于维护
使用场景简单场景复杂场景

二、TrinityCore中的定时器

在TrinityCore中,使用multimap封装实现了两种类型的定时器,分别是EventProcessorTaskScheduler

2.1、EventProcessor:对象专属定时器

设计理念:每个游戏对象拥有独立的定时器系统,管理对象相关的延迟行为。

核心状态机设计

class BasicEvent {enum class AbortState : uint8 {STATE_RUNNING,          // 运行中STATE_ABORT_SCHEDULED,  // 计划中止STATE_ABORTED           // 已中止};// 状态流转:RUNNING → ABORT_SCHEDULED → ABORTED
};

现代化Lambda支持

// 传统继承方式
class SpellCastEvent : public BasicEvent {bool Execute(uint64 e_time, uint32 p_time) override {player->CastSpell(spellId);return true;}
};// 现代Lambda方式(推荐)
eventProcessor.AddEventAtOffset([player, spellId]() {player->CastSpell(spellId);
}, 1500ms);

2.2、EventProcessor在PVP战场中的实际应用

(代码较长,就画大致流程图):

在这里插入图片描述
在这里插入图片描述

2.3、高性能网络集成: EventProcessor与epoll的结合

看了下网络部分代码,发现TrinityCore中,主要采用select来实现网络IO,并没使用epoll,猜想可能是考虑到兼容与跨平台的原因。

/*
* epoll_wait函数原型
* @parameter epfd: epoll实例的文件描述符
* @parameter events: 事件数组,存放就绪的事件
* @parameter maxevents: 事件数组的最大长度
* @parameter timeout: 超时时间,单位毫秒---相当于内置了一个定时器
*/
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)

就用最后一个参数结合EventProcessor来看看能实现什么效果:

核心点:

int32_t EventProcessor::NextExecTime() {if (m_events.empty()) return -1;  // 无限等待auto nextTime = m_events.begin()->first;if (nextTime > m_time) {return nextTime - m_time;     // 精确超时时间}return 0;  // 立即执行
}

和传统方案进行对比:

// 传统方案:固定频率轮询(资源浪费)
void TraditionalLoop() {while (running) {epoll_wait(epfd, events, 64, 10); // 固定10ms超时ProcessNetwork();CheckAllTimers(); // 总是检查所有定时器}
}// 现代方案:智能超时(高效节能)
void ModernLoop() {while (running) {// 动态计算下一个定时器到期时间int timeout = eventProcessor.NextExecTime();epoll_wait(epfd, events, 64, timeout);ProcessNetwork();eventProcessor.Update(timeDiff); // 只处理到期事件}
}

2.4、完整代码

#ifndef __EVENTPROCESSOR_H
#define __EVENTPROCESSOR_H#include <map>
#include <type_traits>
#include <chrono>class EventProcessor;template<typename T>
using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>;using Milliseconds = std::chrono::milliseconds;class BasicEvent
{friend class EventProcessor;enum class AbortState : uint8_t{STATE_RUNNING,              // 正在运行中STATE_ABORT_SCHEDULED,      // 计划终止STATE_ABORTED               // 已终止};
public:BasicEvent():m_abortState(AbortState::STATE_RUNNING), m_addTime(0), m_execTime(0) {}virtual ~BasicEvent(){}                                                    // 析构函数,用于执行事件删除时的操作virtual bool Execute(uint64_t /*e_time*/, uint32_t /*p_time*/) { return true; }    // 当事件被触发时执行该方法virtual bool IsDeletable() const {return true;}                            // 该事件可以被安全删除virtual void Abort(uint64_t /*e_time*/) {}                                    // 当事件被终止时执行该方法void ScheduleAbort();                                                    // 在下一个更新周期终止事件private:void SetAborted();bool IsRunning() const {return (m_abortState == AbortState::STATE_RUNNING);}bool IsAbortScheduled() const {return (m_abortState == AbortState::STATE_ABORT_SCHEDULED);}bool IsAborted() const {return (m_abortState == AbortState::STATE_ABORTED);}uint64_t m_addTime;                                   // 时间戳,事件被添加到队列的时间uint64_t m_execTime;                                  // 计划执行时间AbortState m_abortState;                            // 事件终止状态
};template<typename T>
class LambdaBasicEvent : public BasicEvent
{
public:LambdaBasicEvent(T&& callback) : BasicEvent(), _callback(std::move(callback)) { }bool Execute(uint64_t, uint32_t) override{_callback();return true;}private:T _callback;
};//用lambda进行包装
template<typename T>
using is_lambda_event = std::enable_if_t<!std::is_base_of_v<BasicEvent, std::remove_pointer_t<remove_cvref_t<T>>>>;class EventProcessor
{public:EventProcessor():m_time(0) {}                                // 构造函数virtual ~EventProcessor();                                  // 析构函数,用于执行事件删除时的操作void Update(uint32_t p_time);                                  // 更新时间戳并处理所有到期的事件void KillAllEvents(bool force = false);                      // 终止所有事件void AddEvent(BasicEvent* event, Milliseconds e_time, bool set_addtime = true);   // 向队列中添加一个新事件template<typename T>is_lambda_event<T> AddEvent(T&& event, Milliseconds e_time, bool set_addtime = true) {AddEvent(new LambdaBasicEvent<T>(std::move(event)), e_time, set_addtime);}void AddEventAtOffset(BasicEvent* event, Milliseconds offset) {AddEvent(event, CalculateTime(offset));}template<typename T>is_lambda_event<T> AddEventAtOffset(T&& event, Milliseconds offset) { AddEventAtOffset(new LambdaBasicEvent<T>(std::move(event)), offset); }void ModifyEventTime(BasicEvent* event, Milliseconds newTime);Milliseconds CalculateTime(Milliseconds t_offset) const { return Milliseconds(m_time) + t_offset; }int32_t NextExecTime() {                                    // 返回下一个事件执行时间std::multimap<uint64_t, BasicEvent*>::iterator itr;if ((itr = m_events.begin()) != m_events.end()) {if (itr->first > m_time) {return itr->first - m_time;}return 0;}return -1;}protected:uint64_t m_time;std::multimap<uint64_t, BasicEvent*> m_events;              // 存储容器
};
#endif
#include "EventProcessor.h"using namespace std::chrono_literals;void BasicEvent::ScheduleAbort()
{m_abortState = AbortState::STATE_ABORT_SCHEDULED;
}void BasicEvent::SetAborted()
{m_abortState = AbortState::STATE_ABORTED;
}////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
EventProcessor::~EventProcessor()
{KillAllEvents(true);
}void EventProcessor::Update(uint32_t p_time)
{m_time += p_time;                                                    // 更新时间std::multimap<uint64_t, BasicEvent*>::iterator i;                  // 迭代器i用于遍历事件队列while (((i = m_events.begin()) != m_events.end()) && i->first <= m_time){BasicEvent* event = i->second;                                // 获取并移除事件队列中的第一个事件m_events.erase(i);                                             // 从事件队列中删除该事件if (event->IsRunning()){if (event->Execute(m_time, p_time)){                      // 如果事件正在运行,则执行它delete event;                                          // 完全销毁事件(如果它没有被重新添加)}continue;                                                   // 永远执行}if (event->IsAbortScheduled()){                                  // 如果事件计划终止event->Abort(m_time);                                        // 终止事件event->SetAborted();                                       // 将事件标记为已终止}if (event->IsDeletable()){                                    // 如果事件可以被安全删除delete event;                                             // 销毁事件continue;                                                  // 永远执行}AddEvent(event, CalculateTime(1ms), false);                  // 将事件重新添加到队列中,以便在下一个更新周期检查}
}void EventProcessor::KillAllEvents(bool force)
{for (auto itr = m_events.begin(); itr != m_events.end();){        // 遍历事件队列if (!itr->second->IsAborted()){                                // 如果事件没有被终止itr->second->SetAborted();                                 // 将事件标记为已终止itr->second->Abort(m_time);                                // 终止事件}  if(!force && !itr->second->IsDeletable()){                    // 如果不是强制删除,并且事件不可被安全删除++itr;                                                       // 继续下一个迭代continue;}delete itr->second;                                           // 销毁事件if (force)++itr;                                                      // 在强制模式下清除整个容器elseitr = m_events.erase(itr);                                // 非强制模式时从队列中移除该}if(force){m_events.clear();}
}void EventProcessor::AddEvent(BasicEvent* event, Milliseconds e_time, bool set_addtime)
{if (set_addtime)                                                 // 如果需要设置添加时间event->m_addTime = m_time;                                    // 设置事件添加时间为当前时间event->m_execTime = e_time.count();                              // 设置执行时间m_events.insert(std::pair<uint64_t, BasicEvent*>(e_time.count(), event));  // 将事件插入到队列中,按执行时间排序
}void EventProcessor::ModifyEventTime(BasicEvent* event, Milliseconds newTime)
{for(auto itr = m_events.begin(); itr != m_events.end(); ++itr){    // 遍历事件队列if (itr->second != event)                                       // 如果当前迭代的事件不是要修改时间的事件,则继续下一个迭代continue;event->m_execTime = newTime.count();                          // 设置新执行时间m_events.erase(itr);                                           // 从队列中移除该事件m_events.insert(std::pair<uint64_t, BasicEvent*>(newTime.count(), event));  // 将事件重新插入到队列中,按新执行时间排序break;}
}
#include <chrono>
#include <cstdint>
#include <sys/epoll.h>
#include <memory>
#include <iostream>#include "EventProcessor.h"using namespace std::chrono_literals;std::chrono::steady_clock::time_point appStartTime;inline uint32_t getMSTime() {using namespace std::chrono;return uint32_t(duration_cast<milliseconds>(steady_clock::now()-appStartTime).count());
}inline uint32_t getMSTimeDiff(uint32_t oldMSTime, uint32_t newMSTime) {if (oldMSTime > newMSTime)return (0xffffffff-oldMSTime)+ newMSTime;elsereturn newMSTime - oldMSTime;
}class TestEvent : public BasicEvent {
public:TestEvent() : BasicEvent() {}bool Execute(uint64_t /*e_time*/, uint32_t /*p_time*/) override {std::cout << "Execute TestEvent:" << getMSTime() << std::endl;return true;}
};int main()
{int epfd = epoll_create(1);std::unique_ptr<EventProcessor> evproc = std::make_unique<EventProcessor>();evproc->AddEventAtOffset([](){std::cout << "lambda execute timer." << getMSTime() << std::endl;}, 100ms);evproc->AddEventAtOffset(new TestEvent(), 1s);appStartTime = std::chrono::steady_clock::now();epoll_event evs[64] = {0};uint32_t realCurrTime = 0, realPrevTime = 0;while (true) {int n = epoll_wait(epfd, evs, 64, evproc->NextExecTime());for (int i=0; i<n; i++);realCurrTime = getMSTime();uint32_t diff = getMSTimeDiff(realPrevTime, realCurrTime);evproc->Update(diff);realPrevTime = realCurrTime;}return 0;
}//g++ EventProcessor.cpp main.cpp  -o event -I./ -std=c++17
http://www.dtcms.com/a/486363.html

相关文章:

  • SpringBoot模特兼职网站zu3n3(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • windows开发中使用flutter开发鸿蒙
  • calibre LVS 跑不起来 就将setup 的LVS Option connect下的 connect all nets by name 打开。
  • 向RAGFlow中上传文档到对应的知识库
  • 网站后台发邮件建设网站都需要哪些内容
  • 惠州网站建设 英语外贸论坛有哪些?
  • 【学习笔记10】C++模板编程深度学习(下):可变参数模板与完美转发核心技术
  • 华为盘古 Ultra-MoE-718B-V1.1 正式开放下载!
  • 【OpenHarmony】AI引擎模块架构
  • 为什么选php语言做网站江苏网站建设网络推广
  • 数据结构算法学习:LeetCode热题100-链表篇(上)(相交链表、反转链表、回文链表、环形链表、环形链表 II)
  • STC亮相欧洲区块链大会,碳资产RWA全球化战略迈出关键一步
  • 使用Electron创建helloworld程序
  • 建设校园网站国外研究现状2020网络公司排名
  • DataEase v2 连接 MongoDB 数据源操作说明-MongoDB BI Connector用户创建
  • PHP 8.0+ 编译器级优化与语言运行时演进
  • 网站运营培训网站被百度收录吗
  • 升级到webpack5
  • 【MySQL】MySQL `JSON` 数据类型介绍
  • 通过hutool生成xml
  • vue.config.js 文件功能介绍,使用说明,对应完整示例演示
  • 无极分期网站临沂做网络优化的公司
  • Vue3的路由Router【7】
  • DOM 实例
  • 网站安全建设需求分析报告重庆有哪些科技骗子公司
  • Springboot AOP Aspect 拦截中 获取HttpServletResponse response
  • 【深度学习理论基础】什么是蒙特卡洛算法?有什么作用?
  • 网站建设商虎小程序就业网站建设
  • 从留言板开始做网站企业网站建设代理加盟
  • USB——UVC简介