• 定时器方案,红黑树,时间轮


    定时器应用

    心跳检测 、技能冷却、 倒计时 、其他需要延时处理的功能

    定时器触发方式

    对于服务端来说,驱动服务端业务逻辑的事件包括网络事件、 定时事件、以及信号事件;通常网络事件和定时事件会进行协同处理;定时器触发形式通常有两种:

    1 网络事件和定时事件在一个线程中处理;例如:nginx、 redis、memcached

         定时任务比较少的时候。

         对于多线程情况,引起时间处理不均衡。

         任务多了,会影响网络事件处理。

    2 网络事件和定时事件在不同线程中处理;例如: skynet,...;

         usleep(time)  time要小于最小时间精度

        通常采用的数据结构是  时间轮

        加锁粒度比较小

        时间轮只负责检测,通过信号或者插入执行队列让其他线程处理。

    1. // 网络事件和定时事件在一个线程中处理
    2. while (!quit) {
    3. int timeout = get_nearest_timer() - now();
    4. if (timeout < 0) timeout = -1;
    5. int nevent = epoll_wait(epfd, ev, nev,
    6. timeout);
    7. for (int i=0; i
    8. // ... 处理网络事件
    9. }
    10. // 处理定时事件
    11. update_timer();
    12. }
    13. // 网络事件和定时事件在不同线程中处理
    14. void * thread_timer(void *thread_param) {
    15. init_timer();
    16. while (!quit) {
    17. update_timer();
    18. sleep(t);
    19. }
    20. clear_timer();
    21. return NULL;
    22. }
    23. pthread_create(&pid, NULL, thread_timer,
    24. &thread_param);

    定时器设计

    接口设计

    // 初始化定时器 void init_timer();

    // 添加定时器 Node* add_timer(int expire, callback cb);

    // 删除定时器 bool del_timer(Node* node);

    // 找到最近要触发的定时任务 Node* find_nearest_timer();   // 只有第一种情况才需要

    // 更新检测定时器 void update_timer();

    // 清除定时器   void clear_timer();

    数据结构设计

    对定时任务的组织本质是要处理对定时任务优先级的处理;由此产生两类数据结构;

    按触发时间进行顺序组织

    要求数据结构有序(红黑树、跳表),或者相对有序 (最小堆);

    能快速查找最近触发的定时任务;

    需要考虑怎么处理相同时间触发的定时任务;

    按执行顺序进行组织

    时间轮

    红黑树

    绝对有序 , nginx

    跳表  

    绝对有序,redis未来会引用跳表

    最小堆

    满二叉树:所有的层节点数都是该层所能容纳节点的最大数量 (满足 )

    完全二叉树:若二叉树的深度为 h,去除了 h 层的节点,就是 一个满二叉树;且 h 层都集中在最左侧排列;

    最小堆:

    是一颗完全二叉树;

    某一个节点的值总是小于等于它的子节点的值;

    堆中任意一个节点的子树都是最小堆;

    libevent,libev

    为了满足完全二叉树的定义,往二叉树最高层沿着最左侧添加 一个节点;然后考虑是否能上升操作;如果此时添加值为 4 的 节点,4 节点是 5 节点的左子树;4 比 5 小,4 和 5 需要交换 值;

    最小堆删除节点

    删除操作需要先查找是否包含这个节点;确定存在后,交换最后一个节点,先考虑能否执行下降操作,否则执行上升操作; 最后删除最后一个节点; 例如:删除 1 号节点,则需要下沉操作;删除 9 号节点,则需要上升操作;

    时间轮

    为什么要分成多个层级: 

    1 减少空间占用   

    2 只需要关注最近要触发的定时任务      最近一分钟

    3 按照任务触发的轻重缓急进行组织

    4 减少任务的检测

    tick

    取值范围为时间范围,只需要记录一个指针,当秒针移动一圈,说明下一分钟的任务要进行了。

    任务节点

    expire , callback , next (解决相同触发时间的任务)

    添加节点

    根据time判断放在哪一层, expire = tick0 + time

    重新映射

    当分针的任务移动到秒针层级时

    为什么需要重新映射?        时间精度为秒,只执行秒针层级的任务。

    怎么重新映射:

    1 确定重新映射的位置(算出分针的指针位置)   tick    |      (tick/60)%60

    2 取出该指针指向槽位的所有任务

    3 time = tick0 + time - tick

    4 再添加节点的逻辑。

    删除节点

    1 节点位置可能发生变化

    2 添加一个字段,cancel = true ,任务触发时遇到这个标记不执行具体任务

    时间轮场景

    内核 ,skynet , kafka , netty

     运行图

     原理图

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. using namespace std;
    8. struct TimerNodeBase{
    9. time_t expire;
    10. int64_t id;
    11. };
    12. struct TimerNode: public TimerNodeBase{
    13. using Callback = std::function<void(const TimerNode& node)>;
    14. Callback func;
    15. TimerNode(int64_t id , time_t expire , Callback func):func(func){
    16. this->expire = expire;
    17. this->id = id; // 如果时间相同,根据id的先后顺序执行
    18. };
    19. };
    20. bool operator < (const TimerNodeBase& lhd , const TimerNodeBase& rhd){// std::less<> 需要
    21. if(lhd.expire < rhd.expire)
    22. return true;
    23. else if(lhd.expire > rhd.expire)
    24. return false;
    25. return lhd.id < rhd.id;
    26. }
    27. class Timer{
    28. public:
    29. static time_t GetTick(){
    30. auto sc = chrono::time_point_cast(chrono::steady_clock::now());
    31. auto temp = chrono::duration_cast(sc.time_since_epoch());
    32. return temp.count();
    33. }
    34. TimerNodeBase AddTimer(time_t msec , TimerNode::Callback func){
    35. time_t expire = GetTick() + msec;
    36. auto ele = timermap.emplace(GenID() , expire , func);
    37. return static_cast(*ele.first);
    38. }
    39. bool DelTimer(TimerNodeBase& node){
    40. auto iter = timermap.find(node); // c++14新特性, 传一个相似值 , 基类引用
    41. if(iter != timermap.end()){
    42. timermap.erase(iter);
    43. return true;
    44. }
    45. return false;
    46. }
    47. bool CheckTimer(){ // 检查是否有定时事件
    48. auto iter = timermap.begin();
    49. if(iter != timermap.end() && iter->expire <= GetTick()){ // 时间到了
    50. iter->func(*iter); // 执行任务
    51. timermap.erase(iter);
    52. return true;
    53. }
    54. return false;
    55. }
    56. time_t TimeToSleep(){
    57. auto iter = timermap.begin();
    58. if(iter == timermap.end()) return -1;
    59. time_t diss = iter->expire - GetTick(); // 是否需要睡眠
    60. return diss > 0 ? diss : 0; //
    61. }
    62. private:
    63. static int64_t GenID(){
    64. return gid++;
    65. }
    66. static int64_t gid;
    67. set> timermap;
    68. };
    69. int64_t Timer::gid = 0;
    70. int main(){
    71. int epfd = epoll_create(1);
    72. unique_ptr timer = make_unique();
    73. int i = 0;
    74. timer->AddTimer(1000 , [&](const TimerNode& node){
    75. cout << Timer::GetTick() << "node id:" << node.id << "revoked times:" << ++i << endl;
    76. });
    77. timer->AddTimer(1000, [&](const TimerNode &node) {
    78. cout << Timer::GetTick() << "node id:" << node.id << " revoked times:" << ++i << endl;
    79. });
    80. timer->AddTimer(3000, [&](const TimerNode &node) {
    81. cout << Timer::GetTick() << "node id:" << node.id << " revoked times:" << ++i << endl;
    82. });
    83. auto node = timer->AddTimer(2100, [&](const TimerNode &node) {
    84. cout << Timer::GetTick() << "node id:" << node.id << " revoked times:" << ++i << endl;
    85. });
    86. timer->DelTimer(node);
    87. cout << "now time:" << Timer::GetTick() << endl;
    88. struct epoll_event ev[64] = {0};
    89. while(true){
    90. /*
    91. -1 永久阻塞
    92. 0 没有事件立刻返回,有事件就拷贝到 ev 数组当中
    93. t > 0 阻塞等待 t ms,
    94. timeout 最近触发的定时任务离当前的时间
    95. */
    96. int n = epoll_wait(epfd, ev, 64, timer->TimeToSleep());
    97. for (int i = 0; i < n; i++) {
    98. /*网络事件处理*/
    99. }
    100. /* 处理定时事件*/
    101. while(timer->CheckTimer());
    102. }
    103. return 0;
    104. }
  • 相关阅读:
    2022“杭电杯”中国大学生算法设计超级联赛(5)
    聚观早报 | 极越07正式上市;宝骏云海正式上市
    Java项目:SSM健身房管理系统
    谱聚类原理及Python实现
    交通物流模型 | 基于自监督学习的交通流预测模型
    Oracle 联机日志文件及归档文件
    480439-15-4,一种具有荧光单体的pH敏感性染料Fluorescein O-methacrylate
    SQL游戏行业实战案例5:玩家在线分布(自定义排序,条件求和)
    近半年内连获5家“巨头”投资,这家智能驾驶“黑马”受资本追捧
    【Django-meeting系统】Racher部署教室管理系统的方法(一个pod部署应用、redis、celery)---20221019
  • 原文地址:https://blog.csdn.net/jianfeng123123/article/details/126650349