Ver código fonte

V0.3.1
1、完成了时间轮定时器,但是误差有点大,1秒有60ms的误差

Apple 5 meses atrás
pai
commit
05296d2192
2 arquivos alterados com 267 adições e 132 exclusões
  1. 0 132
      common/Timer/TWTimer.h
  2. 267 0
      common/Timer/TWTimer.hpp

+ 0 - 132
common/Timer/TWTimer.h

@@ -1,132 +0,0 @@
-
-#include <chrono>
-#include <functional>
-#include <list>
-#include <mutex>
-#include <thread>
-#include <vector>
-#include <iostream>
-
-#include "ThreadPool.h"
-#include "spdlog/spdlog.h"
-#include "RingQueue.hpp"
-
-/**
- * @brief 这是一个时间轮定时器的实现,将任务添加到时间轮中,时间轮会在指定的时间点执行任务
- *        任务是一个函数,可以是lambda表达式,也可以是std::bind绑定的函数
- *        任务是挂在一个个时间点上的,同一个时间点可以有多个任务,时间点的最大数目就是任务容器最大容量
- *        间隔时间是1ms,最大定时时间是1000 * 60 * 60 ms,也就是1小时
- * 
- * 缺点:
- *        1、时间轮的时间点是固定的,如果任务的时间点不在时间轮的时间点上,就会被延迟执行
- *        2、任务执行是在这个线程中执行的,如果任务执行时间过长,会影响时间轮的执行
- */
-
-
-/** ==================================================================
- *
- * ================================================================== */
-
-/* 定义函数指针 */
-using TaskFunc = std::function<void()>;
-/* 定义任务结构体 */
-struct Task {
-    bool is_loop;           /* 是否循环定时 */
-    TaskFunc func;          /* 任务函数 */
-    long interval;          /* 定时间隔 */
-};
-
-class TimerWheel {
-public:
-    
-    /* 构造函数,第一个参数是任务时间点容器最大数量,第二个参数是最低检测时间单位 */
-    explicit TimerWheel()
-        : m_current_index(0) {
-        m_wheel_size = 1000 * 60 * 60;      /* 1小时 */
-        m_interval_ms = 1000;               /* 1ms */
-        m_firstLevelWheel.resize(1000);
-        m_secondLevelWheel.resize(60);
-        m_thirdLevelWheel.resize(60);
-    }
-
-    ~TimerWheel() {
-        Stop();
-    }
-    /* 开启时间轮 */
-    void Start() {
-        if (m_running) {
-            return;
-        }
-        m_running = true;
-        /* 开启新线程,定时检测队列 */
-        m_thread = std::thread([this]() {
-            while (m_running) {
-                std::this_thread::sleep_for(std::chrono::milliseconds(m_interval_ms));
-                Tick();
-            }
-        std::cout << "timer oooops!" << std::endl;
-            });
-        m_thread.detach();
-    }
-
-    void Stop() {
-        if (!m_running) {
-            return;
-        }
-        m_running = false;
-        if (m_thread.joinable()) {
-            m_thread.join();
-        }
-    }
-    /* 添加任务函数 */
-    void AddTask(int timeout_ms, Task task, bool is_loop = false) {
-        std::lock_guard<std::mutex> lock(mutex_);
-        /* 计算出需要轮训的次数 */
-        size_t ticks = timeout_ms / m_interval_ms;
-        /* 在当前时间点上加上ticks */
-        size_t index = (m_current_index + ticks) % m_wheel_size;
-        /* 这里是设置在整个时间轮中,该任务在这个时间轮中的分布,以实现循环定时 */
-        size_t allindex = index;
-        for (size_t i = 1 ; allindex < m_wheel_size; i++)
-        {
-            allindex = index * i;
-            if (allindex >= m_wheel_size)
-                break;
-            wheel_[allindex].push_back(task);
-        }
-        
-    }
-
-private:
-    /* 时间片 */
-    void Tick() {
-        std::lock_guard<std::mutex> lock(mutex_);
-        /* 取出这个时间点的函数,循环执行 */
-        auto& tasks = wheel_[m_current_index];
-        for (const auto& task : tasks) {
-            task();
-        }
-        //tasks.clear();
-        /* 可以循环定时的关键,超过了设置的最大时间点,就会重置 */
-        m_current_index = (m_current_index + 1) % m_wheel_size;
-    }
-
-private:
-    long m_max_interval = 0;                /* 最大的定时时间长度,等于下面两个之和,超过就报错 */
-    size_t m_wheel_size;                    /* 时间轮最大的轮训次数 */
-    int m_interval_ms;                      /* 每个时间点的间隔秒数,这里是1ms */
-    long m_firstLevelCount;                 /* 第一层级的时间轮的个数 */
-    long m_secondLevelCount;                /* 第二层级的时间轮的个数 */
-    long m_thirdLevelCount;                 /* 第三层级的时间轮的个数 */
-    std::vector<std::list<Task>> m_firstLevelWheel;     /* 第一层级的时间轮,这里是ms等级,总共1000个 */
-    std::vector<std::list<Task>> m_secondLevelWheel;    /* 第二层级的时间轮,这里是秒的等级,总共60个 */
-    std::vector<std::list<Task>> m_thirdLevelWheel;     /* 第三层级的时间轮,这里是分钟的等级,总共60个 */
-    size_t m_current_index;                 /* 现在的时间点 */
-    bool m_running = false;                 /* 运行标识位 */
-    std::thread m_thread;
-    std::mutex mutex_;
-    
-};
-
-
-

+ 267 - 0
common/Timer/TWTimer.hpp

@@ -0,0 +1,267 @@
+
+#include <chrono>
+#include <functional>
+#include <list>
+#include <mutex>
+#include <thread>
+#include <vector>
+
+#include "ThreadPool/ThreadPool.h"
+#include "FmtLog/fmtlog.h"
+
+/**
+ * @brief 这是一个时间轮定时器的实现,将任务添加到时间轮中,时间轮会在指定的时间点执行任务
+ *        这个定时器的最大定时时间是1小时,最小时间间隔是1ms
+ *        
+ *
+ * 设计说明:
+ *        1、时间轮的时间点是固定的,每个时间点的时间间隔是1ms,最大时间点是1000 * 60 * 60个,也就是1小时
+ *        2、任务是挂在一个个时间点上的,同一个时间点可以有多个任务
+ *        3、时间轮的执行是在一个新的线程中执行的,每隔1ms检测一次是否有任务需要执行
+ *        4、当前时间点是下一个将要执行的时间点,新加入的立即执行的任务就会加入到当前时间点上
+ *        5、时间轮的时间点是循环的,当到达最大时间点时,会重新从0开始
+ *        6、管理线程是100us运行一次,在运行时会进行时间累积,相差1ms就会执行一次任务
+ * 
+ * 缺点:
+ *        1、时间轮的时间点是固定的,如果任务的时间点不在时间轮的时间点上,就会被延迟执行
+ */
+
+
+/** ==================================================================
+ * ************************** 任务结构体 ******************************
+ * ================================================================== */
+
+/**
+ * 任务时间点结构体,用来标识任务在时间轮中的位置
+ */
+struct TaskTimerPoint
+{
+    long firstLevelIndex = 0;
+    long secondLevelIndex = 0;
+    long thirdLevelIndex = 0;
+
+    TaskTimerPoint() = default;
+    TaskTimerPoint& operator=(const TaskTimerPoint& other) {
+        firstLevelIndex = other.firstLevelIndex;
+        secondLevelIndex = other.secondLevelIndex;
+        thirdLevelIndex = other.thirdLevelIndex;
+        return *this;
+    }
+};
+
+/* 定义函数指针 */
+using TaskFunc = std::function<void()>;
+/* 定义任务结构体 */
+struct Task {
+    bool is_loop;           /* 是否循环定时 */
+    long interval;          /* 定时间隔 */
+    TaskFunc func;          /* 任务函数 */
+    TaskTimerPoint timerPoint;   /* 任务时间点,在时间轮中的位置 */
+};
+
+
+
+
+
+/** ==================================================================
+ * ***************************** 任务类 ******************************
+ * ================================================================== */
+
+class TimerWheel {
+public:
+    
+    /* 构造函数,第一个参数是任务时间点容器最大数量,第二个参数是最低检测时间单位 */
+    explicit TimerWheel()
+        : m_currentFirstIndex(0),
+          m_currentSecondIndex(0),
+          m_currentThirdIndex(0),
+          m_currentIndex(0)
+    {
+        m_wheel_size = 1000 * 60 * 60;      /* 1小时定时间隔 */
+        m_interval_ms = 1;                  /* 1ms */
+        m_firstLevelWheel.resize(1000);
+        m_secondLevelWheel.resize(60);
+        m_thirdLevelWheel.resize(60);
+    }
+
+    ~TimerWheel() {
+        Stop();
+    }
+    /* 开启时间轮 */
+    void Start() 
+    {
+        if (m_running) {
+            return;
+        }
+        m_running = true;
+        /* 开启新线程,定时检测队列 */
+        m_thread = std::thread([this]() {
+            std::chrono::steady_clock::time_point lastTime = std::chrono::steady_clock::now();
+            while (m_running) {
+                /* 100us计算一次距离上次执行的间隔时间,超过1ms就执行一次 */
+                std::this_thread::sleep_for(std::chrono::microseconds(m_tick_us));
+                std::chrono::steady_clock::time_point nowTime = std::chrono::steady_clock::now();
+                auto duration = std::chrono::duration_cast<std::chrono::microseconds>(nowTime - lastTime);
+                if(duration.count() >= 900) {
+                    Tick();
+                    lastTime = nowTime;
+                }
+                
+            }
+            FMTLOG_INFO("TimerWheel thread exit");
+        });
+        
+    }
+
+    void Stop() 
+    {
+        if (!m_running) {
+            return;
+        }
+        m_running = false;
+        if (m_thread.joinable()) {
+            m_thread.join();
+        }
+    }
+    /* 绑定任务函数和参数 */
+    template<typename F, typename... Args>
+    std::function<void()> bindTask(int timeout_ms, F&& f, Args&&... args) 
+    {
+        return std::bind(std::forward<F>(f), std::forward<Args>(args)...);
+    }
+    
+    /* 添加任务函数 */
+    void AddTask(long timeout_ms, std::function<void()> func, bool is_loop = false) 
+    {
+        /* 这里需要将当前时间点一起上锁,防止立即执行的任务会错过 */
+        std::lock_guard<std::mutex> lock(m_mutex);
+        /* 计算出定时的时间点数目 */
+        long ticks = timeout_ms / m_interval_ms;
+        if(ticks > m_wheel_size) {
+            FMTLOG_WARN("timeout is too long");
+            return;
+        }
+        Task task;
+        task.is_loop = is_loop;
+        task.interval = timeout_ms;
+        task.func = func;
+        task.timerPoint.firstLevelIndex = 0;
+        task.timerPoint.secondLevelIndex = 0;
+        task.timerPoint.thirdLevelIndex = 0;
+        /* 加上当前时间点的位置偏移,计算出需要插入的时间点 */
+        // long index = m_currentFirstIndex + m_currentSecondIndex * m_firstLevelCount + m_currentThirdIndex * m_secondLevelCount * m_firstLevelCount + ticks;
+        long index = m_currentIndex + ticks;
+        /* 计算出需要的位置,如果超出了最大范围就放到过去的时间点中 */
+        index = index % m_wheel_size;
+        /* 计算出时间点的位置 */
+        task.timerPoint.firstLevelIndex = index % m_firstLevelCount;
+        task.timerPoint.secondLevelIndex = (index / m_firstLevelCount) % m_secondLevelCount;
+        task.timerPoint.thirdLevelIndex = (index / m_firstLevelCount / m_secondLevelCount) % m_thirdLevelCount;
+        /* 加入到时间轮的任务队列中 */
+        if(task.timerPoint.thirdLevelIndex != m_currentThirdIndex)
+        {
+            /* 不在当前正在轮训的第三层时间点内,直接加入到对应的时间点中即可 */
+            m_thirdLevelWheel[task.timerPoint.thirdLevelIndex].push_back(task);
+        }
+        /* 在当前正在轮训的第三层时间点内,继续判断是否已经是过去的时间点 */
+        else 
+        {
+            if(task.timerPoint.secondLevelIndex < m_currentSecondIndex || 
+            (task.timerPoint.secondLevelIndex == m_currentSecondIndex && task.timerPoint.firstLevelIndex < m_currentFirstIndex))
+            {
+                /* 在当前正在轮训的第三层时间点内,但是已经是过去的时间点,加入到当前第三层的队列中 */
+                m_thirdLevelWheel[task.timerPoint.thirdLevelIndex].push_back(task);
+            }
+            /* 在当前第三层还未执行的时间点内 */
+            else if(task.timerPoint.secondLevelIndex > m_currentFirstIndex)
+            {
+                /* 在第二轮未执行的点中,加入到第二轮队列中 */
+                m_secondLevelWheel[task.timerPoint.secondLevelIndex].push_back(task);
+            }
+            else
+            {
+                /* 在当前正在轮训的第二层时间点内,且是未来的时间点,加入到对应的第一层时间点中 */
+                m_firstLevelWheel[task.timerPoint.firstLevelIndex].push_back(task);
+            }
+        }
+        
+    }
+
+private:
+    /* 时间片 */
+    void Tick() 
+    {
+        m_mutex.lock();
+        /* 取出这个时间点的函数,循环执行 */
+        auto listTask = std::move(m_firstLevelWheel[m_currentFirstIndex]);
+        m_firstLevelWheel[m_currentFirstIndex].clear();
+        m_mutex.unlock();
+        /* 将任务加入到线程池中执行 */
+        for (const auto& task : listTask) {
+            CPPTP.add_task(task.func);
+        }
+        /* 将时间点向前 + 1 */
+        m_mutex.lock();
+        m_currentFirstIndex = (m_currentFirstIndex + 1) % m_firstLevelCount;
+        /* 判断第一层时间轮是否走完了 */
+        if(m_currentFirstIndex == 0)
+        {
+            /* 第一个轮走完,取出第二层的下一个时间点 */
+            m_currentSecondIndex = (m_currentSecondIndex + 1) % m_secondLevelCount;
+            if(m_currentSecondIndex == 0)
+            {
+                /* 第二层时间轮也走完了,就取出第三个时间轮的下一个时间点 */
+                m_currentThirdIndex = (m_currentThirdIndex + 1) % m_thirdLevelCount;
+                /* 清空第二层时间轮 */
+                m_secondLevelWheel.clear();
+                for (auto& task : m_thirdLevelWheel[m_currentThirdIndex]) 
+                {
+                    m_secondLevelWheel[task.timerPoint.secondLevelIndex].push_back(task);
+                }
+                m_thirdLevelWheel[m_currentThirdIndex].clear();
+            }
+
+            /* 清空第一层时间轮 */
+            m_firstLevelWheel.clear();
+            for (auto& task : m_secondLevelWheel[m_currentSecondIndex]) 
+            {
+                m_firstLevelWheel[task.timerPoint.firstLevelIndex].push_back(task);
+            }
+            m_secondLevelWheel[m_currentSecondIndex].clear();
+        }
+        m_mutex.unlock();
+        /* 将循环定时的任务重新加入到时间轮中 */
+        for(auto& task : listTask) 
+        {
+            if(task.is_loop)
+            {
+                AddTask(task.interval, task.func, true);
+            }
+        }
+        m_currentIndex = (m_currentIndex + 1) % m_wheel_size;
+        
+    }
+
+private:
+    long m_max_interval = 0;                /* 最大的定时时间长度,等于下面两个之和,超过就报错 */
+    long m_wheel_size;                      /* 时间轮最大的轮训次数 */
+    long m_interval_ms;                     /* 每个时间点的间隔秒数,这里是1ms */
+    const long m_tick_us = 100;                         /* 管理线程跳动时间是100us */
+    const long m_firstLevelCount = 1000;                /* 第一层级的时间轮的个数 */
+    const long m_secondLevelCount = 60;                 /* 第二层级的时间轮的个数 */
+    const long m_thirdLevelCount = 60;                  /* 第三层级的时间轮的个数 */
+    std::vector<std::list<Task>> m_firstLevelWheel;     /* 第一层级的时间轮,这里是ms等级,总共1000个 */
+    std::vector<std::list<Task>> m_secondLevelWheel;    /* 第二层级的时间轮,这里是秒的等级,总共60个 */
+    std::vector<std::list<Task>> m_thirdLevelWheel;     /* 第三层级的时间轮,这里是分钟的等级,总共60个 */
+    long m_currentFirstIndex;               /* 现在的第一层时间点 */
+    long m_currentSecondIndex;              /* 现在的第二层位置 */
+    long m_currentThirdIndex;               /* 现在的第三层位置 */
+    long m_currentIndex;                    /* 现在的时间点 */
+    bool m_running = false;                 /* 运行标志位 */
+    std::thread m_thread;
+    std::mutex m_mutex;
+    
+};
+
+
+