├── .gitignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── CMakeLists.txt ├── README.md ├── main.cpp ├── time_wheel.cpp ├── time_wheel.h ├── time_wheel_scheduler.cpp ├── time_wheel_scheduler.h ├── timer.cpp └── timer.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Compiled Static libraries 20 | *.lai 21 | *.la 22 | *.a 23 | *.lib 24 | 25 | # Executables 26 | *.exe 27 | *.out 28 | *.app 29 | 30 | build/* 31 | log/*.log -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "(gdb) Launch", 9 | "type": "cppdbg", 10 | "request": "launch", 11 | "program": "${workspaceFolder}/build/time-wheel", 12 | "args": [], 13 | "stopAtEntry": false, 14 | "cwd": "${workspaceFolder}", 15 | "environment": [], 16 | "externalConsole": false, 17 | "MIMode": "gdb", 18 | "setupCommands": [ 19 | { 20 | "description": "Enable pretty-printing for gdb", 21 | "text": "-enable-pretty-printing", 22 | "ignoreFailures": true 23 | } 24 | ] 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "ostream": "cpp", 4 | "thread": "cpp", 5 | "array": "cpp", 6 | "atomic": "cpp", 7 | "*.tcc": "cpp", 8 | "cctype": "cpp", 9 | "chrono": "cpp", 10 | "clocale": "cpp", 11 | "cmath": "cpp", 12 | "condition_variable": "cpp", 13 | "cstdarg": "cpp", 14 | "cstddef": "cpp", 15 | "cstdint": "cpp", 16 | "cstdio": "cpp", 17 | "cstdlib": "cpp", 18 | "ctime": "cpp", 19 | "cwchar": "cpp", 20 | "cwctype": "cpp", 21 | "deque": "cpp", 22 | "list": "cpp", 23 | "unordered_map": "cpp", 24 | "unordered_set": "cpp", 25 | "vector": "cpp", 26 | "exception": "cpp", 27 | "algorithm": "cpp", 28 | "functional": "cpp", 29 | "iterator": "cpp", 30 | "memory": "cpp", 31 | "memory_resource": "cpp", 32 | "optional": "cpp", 33 | "ratio": "cpp", 34 | "string": "cpp", 35 | "string_view": "cpp", 36 | "system_error": "cpp", 37 | "tuple": "cpp", 38 | "type_traits": "cpp", 39 | "utility": "cpp", 40 | "fstream": "cpp", 41 | "initializer_list": "cpp", 42 | "iosfwd": "cpp", 43 | "iostream": "cpp", 44 | "istream": "cpp", 45 | "limits": "cpp", 46 | "mutex": "cpp", 47 | "new": "cpp", 48 | "sstream": "cpp", 49 | "stdexcept": "cpp", 50 | "streambuf": "cpp", 51 | "typeinfo": "cpp" 52 | } 53 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "mkdir", 8 | "type": "shell", 9 | "command": "mkdir build -p" 10 | }, 11 | { 12 | "label": "cmake", 13 | "type": "shell", 14 | "command": "cmake -DCMAKE_BUILD_TYPE=debug ..", 15 | "dependsOn": [ 16 | "mkdir" 17 | ], 18 | "options": { 19 | "cwd": "${workspaceFolder}/build" 20 | }, 21 | "problemMatcher": [] 22 | }, 23 | { 24 | "label": "make", 25 | "type": "shell", 26 | "command": "make -j4", 27 | "options": { 28 | "cwd": "${workspaceFolder}/build" 29 | }, 30 | "problemMatcher": [] 31 | }, 32 | { 33 | "label": "run", 34 | "type": "shell", 35 | "command": "${workspaceFolder}/build/time-wheel", 36 | "problemMatcher": [] 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1.0) 2 | 3 | if(POLICY CMP0074) 4 | # find_package() uses _ROOT variables. 5 | # This policy was introduced in CMake version 3.12. 6 | cmake_policy(SET CMP0074 NEW) 7 | endif() 8 | 9 | project(TimeWheel) 10 | 11 | # C++ standard requirements. 12 | set(CMAKE_CXX_STANDARD 11) 13 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 14 | set(CMAKE_CXX_EXTENSIONS OFF) 15 | 16 | # CMake 3.1.0+ required. 17 | # See: https://stackoverflow.com/a/29871891 18 | set(THREADS_PREFER_PTHREAD_FLAG ON) 19 | find_package(Threads REQUIRED) 20 | 21 | include_directories(${PROJECT_SOURCE_DIR}) 22 | 23 | file(GLOB SRCS *.cpp *.h) 24 | 25 | set(EXE_TARGET_NAME time-wheel) 26 | 27 | set(LIBS 28 | "${CMAKE_THREAD_LIBS_INIT}" 29 | ) 30 | 31 | add_executable(${EXE_TARGET_NAME} ${SRCS}) 32 | target_link_libraries(${EXE_TARGET_NAME} ${LIBS}) 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## TimeWheel 2 | 3 | - C++11 implementation of hierarchy time wheel 4 | 5 | - Customizable time wheel. 6 | 7 | - Customizable timer frequency(ms). 8 | 9 | ## Build 10 | 11 | ```bash 12 | $ mkdir build 13 | 14 | $ cd build 15 | 16 | $ cmake -DCMAKE_BUILD_TYPE=debug .. 17 | 18 | $ make -j4 19 | 20 | $ ./build/timw-wheel 21 | ``` 22 | 23 | ### Example 24 | 25 | ```cpp 26 | int main() { 27 | // Four level time wheels: Hour, Minute, Secon, Millisecond. 28 | int timer_step_ms = 50; 29 | TimeWheelScheduler tws(timer_step_ms); 30 | // Hour time wheel. 24 scales, 1 scale = 60 * 60 * 1000 ms. 31 | tws.AppendTimeWheel(24, 60 * 60 * 1000, "HourTimeWheel"); 32 | // Minute time wheel. 60 scales, 1 scale = 60 * 1000 ms. 33 | tws.AppendTimeWheel(60, 60 * 1000, "MinuteTimeWheel"); 34 | // Second time wheel. 60 scales, 1 scale = 1000 ms. 35 | tws.AppendTimeWheel(60, 1000, "SecondTimeWheel"); 36 | // Millisecond time wheel. 1000/timer_step_ms scales, 1 scale = timer_step ms. 37 | tws.AppendTimeWheel(1000 / timer_step_ms, timer_step_ms, "MillisecondTimeWheel"); 38 | 39 | tws.Start(); 40 | 41 | tws.CreateTimerAt(GetNowTimestamp() + 10000, []() { 42 | std::cout << "At now+10s" << std::endl; 43 | }); 44 | 45 | tws.CreateTimerAfter(500, []() { 46 | std::cout << "After 0.5s" << std::endl; 47 | }); 48 | 49 | std::cout << timetoStr() << std::endl; 50 | auto timer_id = tws.CreateTimerEvery(5000, []() { 51 | std::cout << "Every 5s: " << timetoStr() << std::endl; 52 | }); 53 | 54 | tws.CreateTimerEvery(30 * 1000, []() { 55 | std::cout << "Every 30s: " << timetoStr() < 2 | #include 3 | #include 4 | 5 | #include "time_wheel_scheduler.h" 6 | 7 | std::string timetoStr(){ 8 | char tmp[64] = {0}; 9 | time_t t = time(NULL); 10 | tm* tm = localtime(&t); 11 | sprintf(tmp, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec); 12 | return tmp; 13 | } 14 | 15 | int main() { 16 | // Four level time wheels: Hour, Minute, Secon, Millisecond. 17 | int timer_step_ms = 50; 18 | TimeWheelScheduler tws(timer_step_ms); 19 | // Hour time wheel. 24 scales, 1 scale = 60 * 60 * 1000 ms. 20 | tws.AppendTimeWheel(24, 60 * 60 * 1000, "HourTimeWheel"); 21 | // Minute time wheel. 60 scales, 1 scale = 60 * 1000 ms. 22 | tws.AppendTimeWheel(60, 60 * 1000, "MinuteTimeWheel"); 23 | // Second time wheel. 60 scales, 1 scale = 1000 ms. 24 | tws.AppendTimeWheel(60, 1000, "SecondTimeWheel"); 25 | // Millisecond time wheel. 1000/timer_step_ms scales, 1 scale = timer_step ms. 26 | tws.AppendTimeWheel(1000 / timer_step_ms, timer_step_ms, "MillisecondTimeWheel"); 27 | 28 | tws.Start(); 29 | 30 | tws.CreateTimerAt(GetNowTimestamp() + 10000, []() { 31 | std::cout << "At now+10s" << std::endl; 32 | }); 33 | 34 | tws.CreateTimerAfter(500, []() { 35 | std::cout << "After 0.5s" << std::endl; 36 | }); 37 | 38 | std::cout << timetoStr() << std::endl; 39 | auto timer_id = tws.CreateTimerEvery(5000, []() { 40 | std::cout << "Every 5s: " << timetoStr() << std::endl; 41 | }); 42 | 43 | tws.CreateTimerEvery(30 * 1000, []() { 44 | std::cout << "Every 30s: " << timetoStr() <GetCurrentTime(); 17 | } 18 | 19 | return time; 20 | } 21 | 22 | void TimeWheel::AddTimer(TimerPtr timer) { 23 | int64_t less_tw_time = 0; 24 | if (less_level_tw_ != nullptr) { 25 | less_tw_time = less_level_tw_->GetCurrentTime(); 26 | } 27 | int64_t diff = timer->when_ms() + less_tw_time - GetNowTimestamp(); 28 | 29 | // If the difference is greater than scale unit, the timer can be added into the current time wheel. 30 | if (diff >= scale_unit_ms_) { 31 | size_t n = (current_index_ + diff / scale_unit_ms_) % scales_; 32 | slots_[n].push_back(timer); 33 | return; 34 | } 35 | 36 | // If the difference is less than scale uint, the timer should be added into less level time wheel. 37 | if (less_level_tw_ != nullptr) { 38 | less_level_tw_->AddTimer(timer); 39 | return; 40 | } 41 | 42 | // If the current time wheel is the least level, the timer can be added into the current time wheel. 43 | slots_[current_index_].push_back(timer); 44 | } 45 | 46 | void TimeWheel::Increase() { 47 | // Increase the time wheel. 48 | ++current_index_; 49 | if (current_index_ < scales_) { 50 | return; 51 | } 52 | 53 | // If the time wheel is full, the greater level time wheel should be increased. 54 | // The timers in the current slot of the greater level time wheel should be moved into 55 | // the less level time wheel. 56 | current_index_ = current_index_ % scales_; 57 | if (greater_level_tw_ != nullptr) { 58 | greater_level_tw_->Increase(); 59 | std::list slot = std::move(greater_level_tw_->GetAndClearCurrentSlot()); 60 | for (TimerPtr timer : slot) { 61 | AddTimer(timer); 62 | } 63 | } 64 | } 65 | 66 | std::list TimeWheel::GetAndClearCurrentSlot() { 67 | std::list slot; 68 | slot = std::move(slots_[current_index_]); 69 | return slot; 70 | } 71 | -------------------------------------------------------------------------------- /time_wheel.h: -------------------------------------------------------------------------------- 1 | #ifndef TIME_WHEEL_H_ 2 | #define TIME_WHEEL_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "timer.h" 11 | 12 | class TimeWheel { 13 | public: 14 | TimeWheel(uint32_t scales, uint32_t scale_unit_ms, const std::string& name = ""); 15 | 16 | uint32_t scale_unit_ms() const { 17 | return scale_unit_ms_; 18 | } 19 | 20 | uint32_t scales() const { 21 | return scales_; 22 | } 23 | 24 | uint32_t current_index() const { 25 | return current_index_; 26 | } 27 | 28 | void set_less_level_tw(TimeWheel* less_level_tw) { 29 | less_level_tw_ = less_level_tw; 30 | } 31 | 32 | void set_greater_level_tw(TimeWheel* greater_level_tw) { 33 | greater_level_tw_ = greater_level_tw; 34 | } 35 | 36 | int64_t GetCurrentTime() const; 37 | 38 | void AddTimer(TimerPtr timer); 39 | 40 | void Increase(); 41 | 42 | std::list GetAndClearCurrentSlot(); 43 | 44 | private: 45 | std::string name_; 46 | uint32_t current_index_; 47 | 48 | // A time wheel can be devided into multiple scales. A scals has N ms. 49 | uint32_t scales_; 50 | uint32_t scale_unit_ms_; 51 | 52 | // Every slot corresponds to a scale. Every slot contains the timers. 53 | std::vector> slots_; 54 | 55 | TimeWheel* less_level_tw_; // Less scale unit. 56 | TimeWheel* greater_level_tw_; // Greater scale unit. 57 | }; 58 | 59 | using TimeWheelPtr = std::shared_ptr; 60 | 61 | inline int64_t GetNowTimestamp() { 62 | using namespace std::chrono; 63 | auto now = system_clock::now().time_since_epoch(); 64 | return duration_cast(now).count(); 65 | } 66 | 67 | #endif // TIME_WHEEL_H_ 68 | -------------------------------------------------------------------------------- /time_wheel_scheduler.cpp: -------------------------------------------------------------------------------- 1 | #include "time_wheel_scheduler.h" 2 | 3 | static uint32_t s_inc_id = 1; 4 | 5 | TimeWheelScheduler::TimeWheelScheduler(uint32_t timer_step_ms) 6 | : timer_step_ms_(timer_step_ms) 7 | , stop_(false) { 8 | } 9 | 10 | bool TimeWheelScheduler::Start() { 11 | if (timer_step_ms_ < 50) { 12 | return false; 13 | } 14 | 15 | if (time_wheels_.empty()) { 16 | return false; 17 | } 18 | 19 | thread_ = std::thread(std::bind(&TimeWheelScheduler::Run, this)); 20 | 21 | return true; 22 | } 23 | 24 | void TimeWheelScheduler::Run() { 25 | while (true) { 26 | std::this_thread::sleep_for(std::chrono::milliseconds(timer_step_ms_)); 27 | 28 | std::lock_guard lock(mutex_); 29 | if (stop_) { 30 | break; 31 | } 32 | 33 | TimeWheelPtr least_time_wheel = GetLeastTimeWheel(); 34 | least_time_wheel->Increase(); 35 | std::list slot = std::move(least_time_wheel->GetAndClearCurrentSlot()); 36 | for (const TimerPtr& timer : slot) { 37 | auto it = cancel_timer_ids_.find(timer->id()); 38 | if (it != cancel_timer_ids_.end()) { 39 | cancel_timer_ids_.erase(it); 40 | continue; 41 | } 42 | 43 | timer->Run(); 44 | if (timer->repeated()) { 45 | timer->UpdateWhenTime(); 46 | GetGreatestTimeWheel()->AddTimer(timer); 47 | } 48 | } 49 | } 50 | } 51 | 52 | void TimeWheelScheduler::Stop() { 53 | { 54 | std::lock_guard lock(mutex_); 55 | stop_ = true; 56 | } 57 | 58 | thread_.join(); 59 | } 60 | 61 | TimeWheelPtr TimeWheelScheduler::GetGreatestTimeWheel() { 62 | if (time_wheels_.empty()) { 63 | return TimeWheelPtr(); 64 | } 65 | 66 | return time_wheels_.front(); 67 | } 68 | 69 | TimeWheelPtr TimeWheelScheduler::GetLeastTimeWheel() { 70 | if (time_wheels_.empty()) { 71 | return TimeWheelPtr(); 72 | } 73 | 74 | return time_wheels_.back(); 75 | } 76 | 77 | void TimeWheelScheduler::AppendTimeWheel(uint32_t scales, uint32_t scale_unit_ms, const std::string& name) { 78 | TimeWheelPtr time_wheel = std::make_shared(scales, scale_unit_ms, name); 79 | if (time_wheels_.empty()) { 80 | time_wheels_.push_back(time_wheel); 81 | return; 82 | } 83 | 84 | TimeWheelPtr greater_time_wheel = time_wheels_.back(); 85 | greater_time_wheel->set_less_level_tw(time_wheel.get()); 86 | time_wheel->set_greater_level_tw(greater_time_wheel.get()); 87 | time_wheels_.push_back(time_wheel); 88 | } 89 | 90 | uint32_t TimeWheelScheduler::CreateTimerAt(int64_t when_ms, const TimerTask& task) { 91 | if (time_wheels_.empty()) { 92 | return 0; 93 | } 94 | 95 | std::lock_guard lock(mutex_); 96 | ++s_inc_id; 97 | GetGreatestTimeWheel()->AddTimer(std::make_shared(s_inc_id, when_ms, 0, task)); 98 | 99 | return s_inc_id; 100 | } 101 | 102 | uint32_t TimeWheelScheduler::CreateTimerAfter(int64_t delay_ms, const TimerTask& task) { 103 | int64_t when = GetNowTimestamp() + delay_ms; 104 | return CreateTimerAt(when, task); 105 | } 106 | 107 | uint32_t TimeWheelScheduler::CreateTimerEvery(int64_t interval_ms, const TimerTask& task) { 108 | if (time_wheels_.empty()) { 109 | return 0; 110 | } 111 | 112 | std::lock_guard lock(mutex_); 113 | ++s_inc_id; 114 | int64_t when = GetNowTimestamp() + interval_ms; 115 | GetGreatestTimeWheel()->AddTimer(std::make_shared(s_inc_id, when, interval_ms, task)); 116 | 117 | return s_inc_id; 118 | } 119 | 120 | void TimeWheelScheduler::CancelTimer(uint32_t timer_id) { 121 | std::lock_guard lock(mutex_); 122 | cancel_timer_ids_.insert(timer_id); 123 | } 124 | -------------------------------------------------------------------------------- /time_wheel_scheduler.h: -------------------------------------------------------------------------------- 1 | #ifndef TIME_WHEEL_SCHEDULER_H_ 2 | #define TIME_WHEEL_SCHEDULER_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "time_wheel.h" 10 | 11 | class TimeWheelScheduler { 12 | public: 13 | explicit TimeWheelScheduler(uint32_t timer_step_ms = 50); 14 | 15 | // Return timer id. Return 0 if the timer creation fails. 16 | uint32_t CreateTimerAt(int64_t when_ms, const TimerTask& task); 17 | uint32_t CreateTimerAfter(int64_t delay_ms, const TimerTask& task); 18 | uint32_t CreateTimerEvery(int64_t interval_ms, const TimerTask& task); 19 | 20 | void CancelTimer(uint32_t timer_id); 21 | 22 | bool Start(); 23 | void Stop(); 24 | 25 | void AppendTimeWheel(uint32_t scales, uint32_t scale_unit_ms, const std::string& name = ""); 26 | 27 | private: 28 | void Run(); 29 | 30 | TimeWheelPtr GetGreatestTimeWheel(); 31 | TimeWheelPtr GetLeastTimeWheel(); 32 | 33 | private: 34 | std::mutex mutex_; 35 | std::thread thread_; 36 | 37 | bool stop_; 38 | 39 | std::unordered_set cancel_timer_ids_; 40 | 41 | uint32_t timer_step_ms_; 42 | std::vector time_wheels_; 43 | }; 44 | 45 | #endif // TIME_WHEEL_SCHEDULER_H_ 46 | -------------------------------------------------------------------------------- /timer.cpp: -------------------------------------------------------------------------------- 1 | #include "timer.h" 2 | 3 | Timer::Timer(uint32_t id, int64_t when_ms, int64_t interval_ms, const TimerTask& handler) 4 | : interval_ms_(interval_ms) 5 | , repeated_(interval_ms_ > 0) 6 | , when_ms_(when_ms) 7 | , id_(id) 8 | , task_(handler) { 9 | } 10 | 11 | void Timer::Run() { 12 | if (task_) { 13 | task_(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /timer.h: -------------------------------------------------------------------------------- 1 | #ifndef TIMER_H_ 2 | #define TIMER_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | typedef std::function TimerTask; 9 | 10 | class Timer { 11 | public: 12 | Timer(uint32_t id, int64_t when_ms, int64_t interval_ms, const TimerTask& task); 13 | 14 | void Run(); 15 | 16 | uint32_t id() const { 17 | return id_; 18 | } 19 | 20 | int64_t when_ms() const { 21 | return when_ms_; 22 | } 23 | 24 | bool repeated() const { 25 | return repeated_; 26 | } 27 | 28 | void UpdateWhenTime() { 29 | when_ms_ += interval_ms_; 30 | } 31 | 32 | private: 33 | uint32_t id_; 34 | TimerTask task_; 35 | int64_t when_ms_; 36 | uint32_t interval_ms_; 37 | bool repeated_; 38 | }; 39 | 40 | using TimerPtr = std::shared_ptr; 41 | 42 | #endif // TIMER_H_ 43 | --------------------------------------------------------------------------------