• 数组实现定时器


    简述

      定时器是游戏服务器必备的一个功能组件,文章介绍如何用数组结构实现一个定时器。

    数据结构

      每个定时器主要包含以下信息:

    typedef struct TimerInfo
    {
        int id;
        long expired;     //超时时间戳(ms)
        bool is_working;  //用于判断该定时器是否有效
        bool is_repeat;  // 是否为循环定时器标识
        long interval; //循环定时器循环间隔
    
    } TimerInfo;

    id是全局唯一,后面可用于取消未生效的定时器。

    除了以上信息,还应包含定时器要执行的任务(一般是函数闭包),这里为简化代码未写出.

      定时器管理器:

    typedef struct TimerMng
    {
        int id;
        vector<shared_ptr<TimerInfo> > timer_array;   //已序数组
        map<int, shared_ptr<TimerInfo> > timer_map;   //id到定时器的映射 用于根据id取消定时器
    
        mutex mtx;
    
        struct timeval now;
    
        TimerMng()
        {
            id = 0;
        }
    
    } TimerMng;

    timer_array是已序数组,根据定时器超时时间排序。

    timer_map是id到定时器的映射,用于取消未生效的定时器。

    实现代码:

      1 #include <iostream>
      2 #include <sys/time.h>
      3 #include <unistd.h>
      4 #include <thread>
      5 #include <vector>
      6 #include <map>
      7 #include <memory>
      8 #include <mutex>
      9 #include <stdlib.h>
     10 #include <algorithm>
     11 
     12 using namespace std;
     13 
     14 #define THREAD_COUNT 4
     15 
     16 typedef struct TimerInfo
     17 {
     18     int id;
     19     long expired;     //超时时间戳(ms)
     20     bool is_working;  //用于判断该定时器是否有效
     21     bool is_repeat;  // 是否为循环定时器标识
     22     long interval; //循环定时器循环间隔
     23 
     24 } TimerInfo;
     25 
     26 // 排序函数
     27 bool comp(const shared_ptr<TimerInfo> &t1, const shared_ptr<TimerInfo> &t2)
     28 {
     29     if(t1.get()->expired < t2.get()->expired)
     30         return true;
     31     else
     32         return false;
     33 }
     34 
     35 typedef struct TimerMng
     36 {
     37     int id;
     38     vector<shared_ptr<TimerInfo> > timer_array;   //已序数组
     39     map<int, shared_ptr<TimerInfo> > timer_map;   //id到定时器的映射 用于根据id取消定时器
     40 
     41     mutex mtx;
     42 
     43     struct timeval now;
     44 
     45     TimerMng()
     46     {
     47         id = 0;
     48     }
     49 
     50 } TimerMng;
     51 
     52 TimerMng t_mng;
     53 
     54 void add_timer(bool is_repeat, long expired, long interval)
     55 {
     56     TimerInfo *t_info = new TimerInfo();
     57     t_info->is_repeat = is_repeat;
     58     t_info->expired = expired;
     59     t_info->interval = interval;
     60     t_info->is_working = true;
     61 
     62     shared_ptr<TimerInfo> timer_info(t_info);
     63 
     64     lock_guard<mutex> guard(t_mng.mtx);
     65     int now_id = t_mng.id++;
     66     t_info->id = now_id;
     67 
     68     // 二分法寻找合适的插入位置
     69     vector<shared_ptr<TimerInfo> >::iterator pos = lower_bound(t_mng.timer_array.begin(), t_mng.timer_array.end(), timer_info, comp);
     70 
     71     t_mng.timer_array.insert(pos, timer_info);
     72     t_mng.timer_map.insert(pair<int, shared_ptr<TimerInfo> >(now_id, timer_info) );
     73 
     74 }
     75 
     76 void update_time()
     77 {
     78     gettimeofday(&t_mng.now, NULL);
     79 }
     80 
     81 void execute_timer()
     82 {
     83     long now_usec = t_mng.now.tv_sec * 1000 + t_mng.now.tv_usec / 1000;
     84 
     85     while(true) 
     86     {
     87         lock_guard<mutex> guard(t_mng.mtx);
     88 
     89         if (t_mng.timer_array.empty())
     90             break;
     91 
     92         vector<shared_ptr<TimerInfo> >::iterator iter = t_mng.timer_array.begin();
     93 
     94         if (iter->get()->expired > now_usec )
     95             break;
     96 
     97         if (iter->get()->is_working)
     98         {
     99             // do something here
    100             cout << "timer execute succ, now: " << now_usec <<  " id: " << iter->get()->id << " " << "expired: " << iter->get()->expired << endl;
    101         }
    102 
    103         map<int, shared_ptr<TimerInfo> >::iterator map_iter = t_mng.timer_map.find( iter->get()->id);
    104 
    105         // 从map中移除
    106         t_mng.timer_map.erase(map_iter);
    107 
    108         // 从数组中移除
    109         t_mng.timer_array.erase(t_mng.timer_array.begin());
    110 
    111     }
    112 }
    113 
    114 
    115 void timer_thread()
    116 {
    117     while(1)
    118     {
    119         update_time();
    120         execute_timer();
    121         // 1ms 1次循环
    122         usleep(1000);
    123     }
    124 }
    125 
    126 void worker_thread()
    127 {
    128     srand((unsigned)time(0));
    129     while(1)
    130     {
    131         struct timeval now;
    132         gettimeofday(&now, NULL);
    133 
    134         int rand_sec = rand() % 5 + 1;
    135         int rand_usec = rand() % 900000 + 1;
    136 
    137         long expired = (now.tv_sec + rand_sec) * 1000 + ( rand_usec / 1000 );
    138         add_timer(false, expired, 0);
    139 
    140         sleep(rand() % 2 + 1);    
    141     }
    142 }
    143 
    144 
    145 int main()
    146 {
    147     thread t1(timer_thread);
    148 
    149     vector<thread> v_thread;
    150 
    151     for (int i = 0; i < THREAD_COUNT; ++i)
    152     {
    153         v_thread.push_back(thread(worker_thread));
    154     }
    155 
    156     t1.join();
    157 
    158     for (int i = 0; i < THREAD_COUNT; ++i)
    159     {
    160         v_thread[i].join();
    161     }
    162 }


    几个关键点介绍:

    1.  定时器线程执行间隔是1ms,所以加入的定时器的最小循环间隔是1ms。

    2.  add_timer 和 execute_timer对timer_mng的 timer_array 和 timer_map操作前,必须先加锁.

    3.  add_timer是使用二分法(lower_bound)去寻找合适的位置插入新的定时器.

    各个操作效率:

    1. add_timer, 使用二分法寻找 O(log(N)); 数组插入 O(N);  红黑树插入 O(log(N))    ==>   O(N) + 2 * O(log(N)).

    2. execute_timer,  数组删除 O(N); 红黑树删除 O(log(N))        ==>       O(N) + O(log(N)).

    3. cancle_timer(未列出), 红黑树寻找 O(log(N))    ==>    O(log(N)).

    add_timer时数组元素的插入会引起后面元素的移动, execute_timer必定会删除第一个元素,引起后面所有元素的移动,

    这两个是非常耗时的操作,也是数组实现定时器的一大缺点,后面会介绍使用更高效的数据结构去实现.

    Coding Life
  • 相关阅读:
    IP地址 子网掩码 默认网关和DNS服务器的关系
    ios下微信浏览器如何唤醒app?app已上架应用宝
    iOS: 零误差或极小误差的定时执行或延迟执行?
    iOS单例创建的一点疑惑
    Method Swizzing中一般替换方法都写在Category类别里吗?有没有别的实现方式
    相机拍照友盟检测crash是为什么?
    使用google API之前需要對input 做什麼 安全性的處理?
    关于node的聊天室错误
    Node+Deployd+MongoDB安装问题
    array.fliter无法正确过滤出我想要的数组
  • 原文地址:https://www.cnblogs.com/lewiskyo/p/6359789.html
Copyright © 2020-2023  润新知