├── .clang-format ├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── example ├── CMakeLists.txt ├── build.bat ├── build.sh └── main.cpp └── include └── cron_timer.h /.clang-format: -------------------------------------------------------------------------------- 1 | # 语言: None Cpp Java Objc Protp 2 | Language: Cpp 3 | # 基于那个配置文件 4 | BasedOnStyle: LLVM 5 | # 访问说明符的偏移(public private) 6 | AccessModifierOffset: -4 7 | # 括号之后,水平对齐参数: Align DontAlign AlwaysBreak 8 | AlignAfterOpenBracket: DontAlign 9 | # 连续的赋值时,对齐所有的等号 10 | AlignConsecutiveAssignments: false 11 | # 连续声明时,对齐所有声明的变量名 12 | AlignConsecutiveDeclarations: false 13 | # 左对齐换行(使用反斜杠换行)的反斜杠 14 | AlignEscapedNewlinesLeft: true 15 | # 水平对齐二元和三元表达式的操作数 16 | AlignOperands: true 17 | # 对齐连续的尾随的注释 18 | AlignTrailingComments: true 19 | # 允许函数声明的所有参数在放在下一行 20 | AllowAllParametersOfDeclarationOnNextLine: false 21 | # 允许短的块放在同一行 22 | AllowShortBlocksOnASingleLine: true 23 | # 允许短的case标签放在同一行 24 | AllowShortCaseLabelsOnASingleLine: false 25 | # 允许短的函数放在同一行: None, InlineOnly(定义在类中), Empty(空函数), Inline(定义在类中,空函数), All 26 | AllowShortFunctionsOnASingleLine: Inline 27 | # 是否允许短if单行 If true, if (a) return; 可以放到同一行 28 | AllowShortIfStatementsOnASingleLine: false 29 | # 允许短的循环保持在同一行 30 | AllowShortLoopsOnASingleLine: false 31 | # 总是在定义返回类型后换行(deprecated) 32 | AlwaysBreakAfterDefinitionReturnType: None 33 | # 每行字符的限制,0表示没有限制 34 | ColumnLimit: 120 35 | # 描述具有特殊意义的注释的正则表达式,它不应该被分割为多行或以其它方式改变 36 | CommentPragmas: '^ IWYU pragma:' 37 | #指针的*的挨着哪边 38 | PointerAlignment: Left 39 | #缩进宽度 40 | IndentWidth: 4 41 | # 连续的空行保留几行 42 | MaxEmptyLinesToKeep: 1 43 | # 在 @property 后面添加空格, \@property (readonly) 而不是 \@property(readonly). 44 | ObjCSpaceAfterProperty: true 45 | # OC block后面的缩进 46 | ObjCBlockIndentWidth: 4 47 | # 中括号两边空格 [] 48 | SpacesInSquareBrackets: false 49 | # 小括号两边添加空格 50 | SpacesInParentheses : false 51 | #等号两边的空格 52 | SpaceBeforeAssignmentOperators: true 53 | # 容器类的空格 例如 OC的字典 54 | SpacesInContainerLiterals: true 55 | #缩进 56 | IndentWrappedFunctionNames: true 57 | #在block从空行开始 58 | KeepEmptyLinesAtTheStartOfBlocks: true 59 | #在构造函数初始化时按逗号断行,并以冒号对齐 60 | BreakConstructorInitializersBeforeComma: true 61 | #括号后添加空格 62 | SpaceAfterCStyleCast: false 63 | # 允许排序#include, 造成编译错误 64 | SortIncludes: false 65 | # 缩进case 标签 66 | IndentCaseLabels: false 67 | #tab键盘的宽度 68 | TabWidth: 4 69 | UseTab: Always 70 | AlwaysBreakTemplateDeclarations: true 71 | # 在尾随的评论前添加的空格数(只适用于//) 72 | SpacesBeforeTrailingComments: 1 73 | # true 自动检测补全命名空间尾部的大括号 74 | FixNamespaceComments: true -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_size = 4 8 | indent_style = tab 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | -------------------------------------------------------------------------------- /.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 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | .DS_Store 35 | build 36 | example/BuildDebug 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Will 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cron_timer 2 | 3 | 这是一个使用CronTab表达式的定时器,可以在指定时间点触发定时器事件,也可以在一段时间之后触发定时器事件。 4 | 5 | 6 | 7 | ## 待办事项: 8 | 9 | | 编号 | 内容 | 状态 | 10 | | ----- | ------------------------------------------------------------ | --------------------------- | 11 | | 1 | 参考一下 https://www.zhihu.com/question/32251997/answer/1899420964?content_id=1379404441725026304&type=zvideo 优化定时器的触发逻辑 | 20210525,xinyong已完成 | 12 | | 2 | 在Update函数中查找最近的定时器,然后可以等待较长的时间,降低执行次数,提升效率 | 20210525,xinyong已完成 | 13 | | 3 | 日志的时间表示从毫秒改成微秒,让误差更有说服力 | 20210611, xinyong已完成 | 14 | | ~~4~~ | ~~AddTimer的返回值应该由一个weak_ptr来接,当执行完毕之后其引用自动变为null_ptr~~ | ~~20210628, xinyong已完成~~ | 15 | | 5 | 支持在定时器处理函数中取消自己 | 20210721, xinyong已完成 | 16 | | 6 | AddTimer的返回值应该由一个unique_ptr来接,由返回值控制其生命周期 | | 17 | | 7 | 修复未来的CronTimer无法正确触发的bug,感谢[RosenYin](https://github.com/RosenYin)发现bug | 20231111,xinyong已修正 | 18 | | | | | 19 | 20 | 21 | 22 | 23 | 24 | ## 特点: 25 | 26 | 1. 对时间的表达能力强,CronTab表达式已经在Linux平台上广泛使用,可以参考 https://www.bejson.com/othertools/cron/?ivk_sa=1024320u 生成表达式,本组件对这个表达式少有修改,具体可以参考示例代码。 27 | 2. 使用方便,一个头文件搞定一切,拷贝过去就可以使用,不依赖第三方库,Windows、Centos、Ubuntu、Mac都可以运行。一行代码添加一个定时器,可传入成员函数,携带自定义参数。 28 | 3. 精度高、误差不累积,实测误差小于1毫秒。 29 | 4. 性能好,对于定时器内的对象个数,时间判断的时间复杂度做到了O(log(n)),可以支持海量的定时器对象。 30 | 31 | 32 | 33 | ## 使用示例 34 | 35 | ``` 36 | cron_timer::TimerMgr mgr; 37 | 38 | mgr.AddTimer("* * * * * *", [](void) { 39 | // 每秒钟都会执行一次 40 | Log("1 second cron timer hit"); 41 | }); 42 | 43 | mgr.AddTimer("0/3 * * * * *", [](void) { 44 | // 从0秒开始,每3秒钟执行一次 45 | Log("3 second cron timer hit"); 46 | }); 47 | 48 | mgr.AddTimer("0 * * * * *", [](void) { 49 | // 1分钟执行一次(每次都在0秒的时候执行)的定时器 50 | Log("1 minute cron timer hit"); 51 | }); 52 | 53 | mgr.AddTimer("15;30;33 * * * * *", [](void) { 54 | // 指定时间(15秒、30秒和33秒)点都会执行一次 55 | Log("cron timer hit at 15s or 30s or 33s"); 56 | }); 57 | 58 | mgr.AddTimer("40-50 * * * * *", [](void) { 59 | // 指定时间段(40到50内的每一秒)执行的定时器 60 | Log("cron timer hit between 40s to 50s"); 61 | }); 62 | 63 | std::weak_ptr timer = mgr.AddDelayTimer(3000, [](void) { 64 | // 3秒钟之后执行 65 | Log("3 second delay timer hit"); 66 | }); 67 | 68 | // 可以在执行之前取消 69 | if (auto ptr = timer.lock(); ptr != nullptr) { 70 | ptr->Cancel(); 71 | } 72 | 73 | mgr.AddDelayTimer( 74 | 10000, 75 | [](void) { 76 | // 每10秒钟执行一次,总共执行3次 77 | Log("10 second delay timer hit"); 78 | }, 79 | 3); 80 | Log("10 second delay timer added"); 81 | 82 | while (!_shutDown) { 83 | auto nearest_timer = 84 | (std::min)(std::chrono::system_clock::now() + std::chrono::milliseconds(500), mgr.GetNearestTime()); 85 | std::this_thread::sleep_until(nearest_timer); 86 | mgr.Update(); 87 | } 88 | 89 | ``` 90 | 91 | 92 | 93 | ## 测试数据 94 | 95 | 精度有多高,**误差小于1毫秒**,数据说话: 96 | 97 | ``` 98 | 2021-06-11 22:25:31.017870 10 second delay timer added 99 | 2021-06-11 22:25:31.017937 1 second cron timer hit 100 | 2021-06-11 22:25:32.000337 1 second cron timer hit 101 | 2021-06-11 22:25:33.000450 3 second cron timer hit 102 | 2021-06-11 22:25:33.000508 cron timer hit at 15s or 30s or 33s 103 | 2021-06-11 22:25:33.000534 1 second cron timer hit 104 | 2021-06-11 22:25:34.000368 1 second cron timer hit 105 | 2021-06-11 22:25:35.000398 1 second cron timer hit 106 | 2021-06-11 22:25:36.000455 3 second cron timer hit 107 | 2021-06-11 22:25:36.000512 1 second cron timer hit 108 | 2021-06-11 22:25:37.000385 1 second cron timer hit 109 | 2021-06-11 22:25:38.000349 1 second cron timer hit 110 | 2021-06-11 22:25:39.000380 3 second cron timer hit 111 | 2021-06-11 22:25:39.000449 1 second cron timer hit 112 | 2021-06-11 22:25:40.000544 cron timer hit between 40s to 50s 113 | 2021-06-11 22:25:40.000603 1 second cron timer hit 114 | 2021-06-11 22:25:41.000307 cron timer hit between 40s to 50s 115 | 2021-06-11 22:25:41.000361 1 second cron timer hit 116 | 2021-06-11 22:25:41.017942 10 second delay timer hit 117 | 2021-06-11 22:25:42.000431 3 second cron timer hit 118 | 2021-06-11 22:25:42.000488 cron timer hit between 40s to 50s 119 | 2021-06-11 22:25:42.000525 1 second cron timer hit 120 | 2021-06-11 22:25:43.000369 cron timer hit between 40s to 50s 121 | 2021-06-11 22:25:43.000425 1 second cron timer hit 122 | 2021-06-11 22:25:44.000370 cron timer hit between 40s to 50s 123 | 2021-06-11 22:25:44.000424 1 second cron timer hit 124 | 2021-06-11 22:25:45.000447 3 second cron timer hit 125 | 2021-06-11 22:25:45.000505 cron timer hit between 40s to 50s 126 | 2021-06-11 22:25:45.000532 1 second cron timer hit 127 | 2021-06-11 22:25:46.000357 cron timer hit between 40s to 50s 128 | 2021-06-11 22:25:46.000411 1 second cron timer hit 129 | 2021-06-11 22:25:47.000355 cron timer hit between 40s to 50s 130 | 2021-06-11 22:25:47.000408 1 second cron timer hit 131 | 2021-06-11 22:25:48.000438 3 second cron timer hit 132 | 2021-06-11 22:25:48.000496 cron timer hit between 40s to 50s 133 | 2021-06-11 22:25:48.000522 1 second cron timer hit 134 | 135 | ``` 136 | 137 | 138 | 139 | ## 一些思路: 140 | 141 | 1.为什么没有使用时间轮? 142 | 143 | 答:时间轮定时器作为延时触发是很合适的,时间复杂度都是O(1)的,但是对于在固定时间点触发的定时器,我觉得会有累积误差,我暂时还没找到解决的方法,所以使用了红黑树,红黑树的时间复杂度是O(log(N))的,性能也不错。 144 | 145 | 2.为什么误差能做到1毫秒以下? 146 | 147 | 答:对于定时器组件,需要提供一个“获取最近即将触发的时间”这样一个接口,有了这样一个接口,在消息队列中就可以做到堆这个时间的精确等待,这个等待操作不是简单的sleep几毫秒,是使用了CPU硬件提供的计时器功能,超时的时候触发一个中断。当然重要的是红黑树LogN的时间复杂度在发挥作用。 148 | 149 | 150 | 151 | **如果您觉得不错,感谢Star,如果您觉得有问题,欢迎提issue** 152 | 153 | [![Star History Chart](https://api.star-history.com/svg?repos=yongxin-ms/cron_timer&type==Date)](https://star-history.com/#yongxin-ms/cron_timer&Date) 154 | -------------------------------------------------------------------------------- /example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | 3 | set(PROJECT_NAME CronTimerExample) 4 | project(${PROJECT_NAME}) 5 | 6 | set(CMAKE_CXX_STANDARD 17) 7 | 8 | if (CMAKE_SYSTEM_NAME MATCHES "Linux") 9 | set(CMAKE_CXX_FLAGS "-g -Wall -pthread") 10 | elseif (CMAKE_SYSTEM_NAME MATCHES "Windows") 11 | #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4819 /wd4267") 12 | set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME}) 13 | endif () 14 | 15 | INCLUDE_DIRECTORIES( 16 | ${CMAKE_CURRENT_SOURCE_DIR} 17 | ${CMAKE_CURRENT_SOURCE_DIR}/../include 18 | ) 19 | 20 | set(SOURCE_FILES 21 | ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp 22 | ) 23 | 24 | set(CRON_TIMER_SRC 25 | ${CMAKE_CURRENT_SOURCE_DIR}/../include/cron_timer.h 26 | ) 27 | 28 | source_group(source FILES ${SOURCE_FILES}) 29 | source_group(source\\CronTimer FILES ${CRON_TIMER_SRC}) 30 | 31 | add_executable(${PROJECT_NAME} 32 | ${SOURCE_FILES} 33 | ${CRON_TIMER_SRC} 34 | ) 35 | -------------------------------------------------------------------------------- /example/build.bat: -------------------------------------------------------------------------------- 1 | @set trunk_dir=%cd% 2 | pushd %trunk_dir% 3 | 4 | md %trunk_dir%\build 5 | cd %trunk_dir%\build 6 | cmake %trunk_dir% 7 | 8 | popd -------------------------------------------------------------------------------- /example/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | basepath=$(cd `dirname $0`; pwd) 3 | dir=$basepath/BuildDebug 4 | mkdir -p "$dir" 5 | cd "$dir" 6 | cmake "-DCMAKE_BUILD_TYPE=Debug" .. 7 | make -j8 8 | -------------------------------------------------------------------------------- /example/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #ifdef _WIN32 8 | #define WIN32_LEAN_AND_MEAN 9 | #include 10 | #else 11 | #include 12 | #endif 13 | 14 | #include "../include/cron_timer.h" 15 | 16 | std::atomic_bool _shutDown; 17 | 18 | #ifdef _WIN32 19 | BOOL WINAPI ConsoleHandler(DWORD CtrlType) { 20 | switch (CtrlType) { 21 | case CTRL_C_EVENT: 22 | case CTRL_CLOSE_EVENT: 23 | case CTRL_LOGOFF_EVENT: 24 | case CTRL_SHUTDOWN_EVENT: 25 | _shutDown = true; 26 | break; 27 | 28 | default: 29 | break; 30 | } 31 | 32 | return TRUE; 33 | } 34 | #else 35 | void signal_hander(int signo) { 36 | printf("catch a signal:%d\n:", signo); 37 | _shutDown = true; 38 | } 39 | #endif 40 | 41 | std::string FormatDateTime(const std::chrono::system_clock::time_point& time) { 42 | uint64_t micro = std::chrono::duration_cast(time.time_since_epoch()).count() - 43 | std::chrono::duration_cast(time.time_since_epoch()).count() * 1000000; 44 | char _time[64] = {0}; 45 | time_t tt = std::chrono::system_clock::to_time_t(time); 46 | struct tm local_time; 47 | 48 | #ifdef _WIN32 49 | localtime_s(&local_time, &tt); 50 | #else 51 | localtime_r(&tt, &local_time); 52 | #endif // _WIN32 53 | 54 | std::snprintf(_time, sizeof(_time), "%d-%02d-%02d %02d:%02d:%02d.%06ju", local_time.tm_year + 1900, 55 | local_time.tm_mon + 1, local_time.tm_mday, local_time.tm_hour, local_time.tm_min, local_time.tm_sec, micro); 56 | return std::string(_time); 57 | } 58 | 59 | void Log(const char* fmt, ...) { 60 | char buf[4096]; 61 | va_list args; 62 | va_start(args, fmt); 63 | vsnprintf(buf, sizeof(buf) - 1, fmt, args); 64 | va_end(args); 65 | 66 | std::string time_now = FormatDateTime(std::chrono::system_clock::now()); 67 | printf("%s %s\n", time_now.c_str(), buf); 68 | } 69 | 70 | void TestSplitStr() { 71 | std::vector v; 72 | assert(cron_timer::Text::SplitStr(v, "", ' ') == 0); 73 | assert(cron_timer::Text::SplitStr(v, " ", ' ') == 0); 74 | assert(cron_timer::Text::SplitStr(v, "a", ' ') == 1); 75 | assert(cron_timer::Text::SplitStr(v, "abcc", ' ') == 1); 76 | assert(cron_timer::Text::SplitStr(v, "abc def", ' ') == 2); 77 | assert(cron_timer::Text::SplitStr(v, " abc def", ' ') == 2); 78 | assert(cron_timer::Text::SplitStr(v, " abc def ", ' ') == 2); 79 | assert(cron_timer::Text::SplitStr(v, " abc def ", ' ') == 2); 80 | 81 | assert(cron_timer::Text::ParseParam(v, "", ',') == 1); 82 | assert(cron_timer::Text::ParseParam(v, " ", ',') == 1); 83 | assert(cron_timer::Text::ParseParam(v, "a", ',') == 1); 84 | assert(cron_timer::Text::ParseParam(v, "abcc", ',') == 1); 85 | assert(cron_timer::Text::ParseParam(v, "abc,def", ',') == 2); 86 | assert(cron_timer::Text::ParseParam(v, "abc,,def", ',') == 3); 87 | assert(cron_timer::Text::ParseParam(v, ",,abc,,,def,,", ',') == 8); 88 | assert(cron_timer::Text::ParseParam(v, ",,,", ',') == 4); 89 | } 90 | 91 | void TestCronTimerInMainThread() { 92 | cron_timer::TimerMgr mgr; 93 | 94 | mgr.AddTimer("* * * * * *", [](void) { 95 | // every second 96 | Log("1 second cron timer hit"); 97 | }); 98 | 99 | mgr.AddTimer("0/3 * * * * *", [](void) { 100 | // every 3 seconds 101 | Log("3 second cron timer hit"); 102 | }); 103 | 104 | mgr.AddTimer("0 * * * * *", [](void) { 105 | // every minute 106 | Log("1 minute cron timer hit"); 107 | }); 108 | 109 | mgr.AddTimer("15;30;33 * * * * *", [](void) { 110 | // specific second 111 | Log("cron timer hit at 15s or 30s or 33s"); 112 | }); 113 | 114 | mgr.AddTimer("40-50 * * * * *", [](void) { 115 | // seconds interval 116 | Log("cron timer hit between 40s to 50s"); 117 | }); 118 | 119 | std::weak_ptr timer = mgr.AddDelayTimer(3000, [](void) { 120 | // after 3 seconds 121 | Log("3 second delay timer hit"); 122 | }); 123 | 124 | // can be cancelled 125 | if (auto ptr = timer.lock(); ptr != nullptr) { 126 | ptr->Cancel(); 127 | } 128 | 129 | mgr.AddDelayTimer( 130 | 10000, 131 | [](void) { 132 | // every 10 seconds for 3 times 133 | Log("10 second delay timer hit"); 134 | }, 135 | 3); 136 | Log("10 second delay timer added"); 137 | 138 | while (!_shutDown) { 139 | auto nearest_timer = 140 | (std::min)(std::chrono::system_clock::now() + std::chrono::milliseconds(500), mgr.GetNearestTime()); 141 | std::this_thread::sleep_until(nearest_timer); 142 | mgr.Update(); 143 | } 144 | } 145 | 146 | int main() { 147 | #ifdef _WIN32 148 | SetConsoleCtrlHandler(ConsoleHandler, TRUE); 149 | EnableMenuItem(GetSystemMenu(GetConsoleWindow(), false), SC_CLOSE, MF_GRAYED | MF_BYCOMMAND); 150 | #else 151 | signal(SIGINT, signal_hander); 152 | #endif 153 | 154 | TestSplitStr(); 155 | TestCronTimerInMainThread(); 156 | return 0; 157 | } 158 | -------------------------------------------------------------------------------- /include/cron_timer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | /* 13 | cron_timer::TimerMgr mgr; 14 | 15 | mgr.AddTimer("* * * * * *", [](void) { 16 | Log("1 second cron timer hit"); 17 | }); 18 | 19 | mgr.AddTimer("0/3 * * * * *", [](void) { 20 | Log("3 second cron timer hit"); 21 | }); 22 | 23 | mgr.AddTimer("0 * * * * *", [](void) { 24 | Log("1 minute cron timer hit"); 25 | }); 26 | 27 | mgr.AddTimer("15;30;33 * * * * *", [](void) { 28 | Log("cron timer hit at 15s or 30s or 33s"); 29 | }); 30 | 31 | mgr.AddTimer("40-50 * * * * *", [](void) { 32 | Log("cron timer hit between 40s to 50s"); 33 | }); 34 | 35 | std::weak_ptr timer = mgr.AddDelayTimer(3000, [](void) { 36 | Log("3 second delay timer hit"); 37 | }); 38 | 39 | if (auto ptr = timer.lock(); ptr != nullptr) { 40 | ptr->Cancel(); 41 | } 42 | 43 | mgr.AddDelayTimer( 44 | 10000, 45 | [](void) { 46 | Log("10 second delay timer hit"); 47 | }, 48 | 3); 49 | Log("10 second delay timer added"); 50 | 51 | while (!_shutDown) { 52 | auto nearest_timer = 53 | (std::min)(std::chrono::system_clock::now() + std::chrono::milliseconds(500), mgr.GetNearestTime()); 54 | std::this_thread::sleep_until(nearest_timer); 55 | mgr.Update(); 56 | } 57 | */ 58 | 59 | namespace cron_timer { 60 | class Text { 61 | public: 62 | // Used to split strings separated by spaces, consecutive spaces are counted as a separator 63 | static size_t SplitStr(std::vector& os, const std::string& is, char c) { 64 | os.clear(); 65 | auto start = is.find_first_not_of(c, 0); 66 | while (start != std::string::npos) { 67 | auto end = is.find_first_of(c, start); 68 | if (end == std::string::npos) { 69 | os.emplace_back(is.substr(start)); 70 | break; 71 | } else { 72 | os.emplace_back(is.substr(start, end - start)); 73 | start = is.find_first_not_of(c, end + 1); 74 | } 75 | } 76 | return os.size(); 77 | } 78 | 79 | static size_t SplitInt(std::vector& number_result, const std::string& is, char c) { 80 | std::vector string_result; 81 | SplitStr(string_result, is, c); 82 | 83 | number_result.clear(); 84 | for (size_t i = 0; i < string_result.size(); i++) { 85 | const std::string& value = string_result[i]; 86 | number_result.emplace_back(atoi(value.data())); 87 | } 88 | 89 | return number_result.size(); 90 | } 91 | 92 | static std::vector ParseParam(const std::string& is, char c) { 93 | std::vector result; 94 | ParseParam(result, is, c); 95 | return result; 96 | } 97 | 98 | // Used to segment strings separated by commas, consecutive commas are counted as multiple delimiters 99 | static size_t ParseParam(std::vector& result, const std::string& is, char c) { 100 | result.clear(); 101 | size_t start = 0; 102 | while (start < is.size()) { 103 | auto end = is.find_first_of(c, start); 104 | if (end != std::string::npos) { 105 | result.emplace_back(is.substr(start, end - start)); 106 | start = end + 1; 107 | } else { 108 | result.emplace_back(is.substr(start)); 109 | break; 110 | } 111 | } 112 | 113 | if (start == is.size()) { 114 | result.emplace_back(std::string()); 115 | } 116 | return result.size(); 117 | } 118 | }; 119 | 120 | class CronExpression { 121 | public: 122 | enum DATA_TYPE { 123 | DT_SECOND = 0, 124 | DT_MINUTE = 1, 125 | DT_HOUR = 2, 126 | DT_DAY_OF_MONTH = 3, 127 | DT_MONTH = 4, 128 | DT_YEAR = 5, 129 | DT_MAX, 130 | }; 131 | 132 | static bool GetValues(const std::string& input, DATA_TYPE data_type, std::vector& values) { 133 | 134 | // 135 | // attention: enum seperater is ';' not ',' for using it in csv 136 | // 137 | 138 | static const char CRON_SEPERATOR_ENUM = ';'; 139 | static const char CRON_SEPERATOR_RANGE = '-'; 140 | static const char CRON_SEPERATOR_INTERVAL = '/'; 141 | 142 | if (input == "*") { 143 | auto pair_range = GetRangeFromType(data_type); 144 | for (auto i = pair_range.first; i <= pair_range.second; ++i) { 145 | values.push_back(i); 146 | } 147 | } else if (input.find_first_of(CRON_SEPERATOR_ENUM) != std::string::npos) { 148 | // enum 149 | std::vector v; 150 | Text::SplitInt(v, input, CRON_SEPERATOR_ENUM); 151 | std::pair pair_range = GetRangeFromType(data_type); 152 | for (auto value : v) { 153 | if (value < pair_range.first || value > pair_range.second) { 154 | return false; 155 | } 156 | values.push_back(value); 157 | } 158 | } else if (input.find_first_of(CRON_SEPERATOR_RANGE) != std::string::npos) { 159 | // range 160 | std::vector v; 161 | Text::SplitInt(v, input, CRON_SEPERATOR_RANGE); 162 | if (v.size() != 2) { 163 | return false; 164 | } 165 | 166 | int from = v[0]; 167 | int to = v[1]; 168 | std::pair pair_range = GetRangeFromType(data_type); 169 | if (from < pair_range.first || to > pair_range.second) { 170 | return false; 171 | } 172 | 173 | for (int i = from; i <= to; i++) { 174 | values.push_back(i); 175 | } 176 | } else if (input.find_first_of(CRON_SEPERATOR_INTERVAL) != std::string::npos) { 177 | // interval 178 | std::vector v; 179 | Text::SplitInt(v, input, CRON_SEPERATOR_INTERVAL); 180 | if (v.size() != 2) { 181 | return false; 182 | } 183 | 184 | int from = v[0]; 185 | int interval = v[1]; 186 | std::pair pair_range = GetRangeFromType(data_type); 187 | if (from < pair_range.first || interval < 0) { 188 | return false; 189 | } 190 | 191 | for (int i = from; i <= pair_range.second; i += interval) { 192 | values.push_back(i); 193 | } 194 | } else { 195 | // specific value 196 | std::pair pair_range = GetRangeFromType(data_type); 197 | int value = atoi(input.data()); 198 | if (value < pair_range.first || value > pair_range.second) { 199 | return false; 200 | } 201 | 202 | values.push_back(value); 203 | } 204 | 205 | assert(values.size() > 0); 206 | return values.size() > 0; 207 | } 208 | 209 | private: 210 | static std::pair GetRangeFromType(DATA_TYPE data_type) { 211 | int from = 0; 212 | int to = 0; 213 | 214 | switch (data_type) { 215 | case CronExpression::DT_SECOND: 216 | case CronExpression::DT_MINUTE: 217 | from = 0; 218 | to = 59; 219 | break; 220 | case CronExpression::DT_HOUR: 221 | from = 0; 222 | to = 23; 223 | break; 224 | case CronExpression::DT_DAY_OF_MONTH: 225 | from = 1; 226 | to = 31; 227 | break; 228 | case CronExpression::DT_MONTH: 229 | from = 1; 230 | to = 12; 231 | break; 232 | case CronExpression::DT_YEAR: 233 | from = 1970; 234 | to = 2099; 235 | break; 236 | case CronExpression::DT_MAX: 237 | assert(false); 238 | break; 239 | } 240 | 241 | return std::make_pair(from, to); 242 | } 243 | }; 244 | 245 | class TimerMgr; 246 | class BaseTimer; 247 | using FUNC_CALLBACK = std::function; 248 | using TimerPtr = std::shared_ptr; 249 | 250 | class BaseTimer : public std::enable_shared_from_this { 251 | friend class TimerMgr; 252 | 253 | public: 254 | BaseTimer(TimerMgr& owner, FUNC_CALLBACK&& func, int count) 255 | : m_owner(owner) 256 | , m_func(std::move(func)) 257 | , m_triggerTime(std::chrono::system_clock::now()) 258 | , m_countLeft(count) 259 | , m_canceled(false) {} 260 | virtual ~BaseTimer() {} 261 | inline void Cancel(); 262 | 263 | // trigger time of the timer 264 | std::chrono::system_clock::time_point GetTriggerTime() const { return m_triggerTime; } 265 | 266 | private: 267 | virtual void CreateTriggerTime(bool next) = 0; 268 | inline void DoFunc(); 269 | 270 | protected: 271 | TimerMgr& m_owner; 272 | FUNC_CALLBACK m_func; 273 | std::chrono::system_clock::time_point m_triggerTime; 274 | int m_countLeft; 275 | bool m_canceled; 276 | }; 277 | 278 | struct CronWheel { 279 | CronWheel() 280 | : cur_index(0) {} 281 | 282 | size_t cur_index; 283 | std::vector values; 284 | }; 285 | 286 | class CronTimer : public BaseTimer { 287 | friend class TimerMgr; 288 | 289 | public: 290 | CronTimer(TimerMgr& owner, std::vector&& wheels, FUNC_CALLBACK&& func, int count) 291 | : BaseTimer(owner, std::move(func), count) 292 | , m_wheels(std::move(wheels)) { 293 | tm local_tm; 294 | time_t time_now = time(nullptr); 295 | 296 | #ifdef _WIN32 297 | localtime_s(&local_tm, &time_now); 298 | #else 299 | localtime_r(&time_now, &local_tm); 300 | #endif // _WIN32 301 | 302 | std::vector init_values; 303 | init_values.push_back(local_tm.tm_sec); 304 | init_values.push_back(local_tm.tm_min); 305 | init_values.push_back(local_tm.tm_hour); 306 | init_values.push_back(local_tm.tm_mday); 307 | init_values.push_back(local_tm.tm_mon + 1); 308 | init_values.push_back(local_tm.tm_year + 1900); 309 | 310 | std::pair pairValue = std::make_pair(0, false); 311 | for (int i = CronExpression::DT_YEAR; i >= 0; i--) { 312 | pairValue = GetMinValid(i, init_values[i], pairValue.second); 313 | m_wheels[i].cur_index = pairValue.first; 314 | } 315 | } 316 | 317 | private: 318 | virtual void CreateTriggerTime(bool next) { 319 | if (next) { 320 | Next(CronExpression::DT_SECOND); 321 | } 322 | 323 | tm next_tm; 324 | memset(&next_tm, 0, sizeof(next_tm)); 325 | next_tm.tm_sec = GetCurValue(CronExpression::DT_SECOND); 326 | next_tm.tm_min = GetCurValue(CronExpression::DT_MINUTE); 327 | next_tm.tm_hour = GetCurValue(CronExpression::DT_HOUR); 328 | next_tm.tm_mday = GetCurValue(CronExpression::DT_DAY_OF_MONTH); 329 | next_tm.tm_mon = GetCurValue(CronExpression::DT_MONTH) - 1; 330 | next_tm.tm_year = GetCurValue(CronExpression::DT_YEAR) - 1900; 331 | 332 | m_triggerTime = std::chrono::system_clock::from_time_t(mktime(&next_tm)); 333 | } 334 | 335 | // move to the next trigger time 336 | void Next(int data_type) { 337 | if (data_type >= CronExpression::DT_MAX) { 338 | // overflowed, this timer is invalid, should be removed 339 | m_canceled = true; 340 | return; 341 | } 342 | 343 | auto& wheel = m_wheels[data_type]; 344 | if (wheel.cur_index == wheel.values.size() - 1) { 345 | wheel.cur_index = 0; 346 | Next(data_type + 1); 347 | } else { 348 | ++wheel.cur_index; 349 | } 350 | } 351 | 352 | // return index, is changed 353 | std::pair GetMinValid(int data_type, int value, bool changed) const { 354 | auto& wheel = m_wheels[data_type]; 355 | if (changed) { 356 | return std::make_pair(0, true); 357 | } 358 | 359 | for (size_t i = 0; i < wheel.values.size(); i++) { 360 | if (wheel.values[i] < value) { 361 | continue; 362 | } else if (wheel.values[i] == value) { 363 | return std::make_pair(i, false); 364 | } else { 365 | return std::make_pair(i, true); 366 | } 367 | } 368 | 369 | return std::make_pair(0, true); 370 | } 371 | 372 | int GetCurValue(int data_type) const { 373 | const auto& wheel = m_wheels[data_type]; 374 | return wheel.values[wheel.cur_index]; 375 | } 376 | 377 | private: 378 | std::vector m_wheels; 379 | }; 380 | 381 | class LaterTimer : public BaseTimer { 382 | friend class TimerMgr; 383 | 384 | public: 385 | LaterTimer(TimerMgr& owner, int milliseconds, FUNC_CALLBACK&& func, int count) 386 | : BaseTimer(owner, std::move(func), count) 387 | , m_milliSeconds(milliseconds) {} 388 | 389 | private: 390 | virtual void CreateTriggerTime(bool next) { m_triggerTime += std::chrono::milliseconds(m_milliSeconds); } 391 | 392 | private: 393 | const int m_milliSeconds; 394 | }; 395 | 396 | class TimerMgr { 397 | friend class BaseTimer; 398 | friend class CronTimer; 399 | friend class LaterTimer; 400 | 401 | public: 402 | TimerMgr() {} 403 | TimerMgr(const TimerMgr&) = delete; 404 | const TimerMgr& operator=(const TimerMgr&) = delete; 405 | 406 | void Stop() { m_timers.clear(); } 407 | 408 | enum { 409 | RUN_FOREVER = 0, 410 | }; 411 | 412 | TimerPtr AddTimer(const std::string& timer_string, FUNC_CALLBACK&& func, int count = RUN_FOREVER) { 413 | std::vector v; 414 | Text::SplitStr(v, timer_string, ' '); 415 | if (v.size() != CronExpression::DT_MAX) { 416 | assert(false); 417 | return nullptr; 418 | } 419 | 420 | std::vector wheels; 421 | for (int i = 0; i < CronExpression::DT_MAX; i++) { 422 | const auto& expression = v[i]; 423 | CronExpression::DATA_TYPE data_type = CronExpression::DATA_TYPE(i); 424 | CronWheel wheel; 425 | if (!CronExpression::GetValues(expression, data_type, wheel.values)) { 426 | assert(false); 427 | return nullptr; 428 | } 429 | 430 | wheels.emplace_back(wheel); 431 | } 432 | 433 | auto p = std::make_shared(*this, std::move(wheels), std::move(func), count); 434 | p->CreateTriggerTime(false); 435 | insert(p); 436 | return p; 437 | } 438 | 439 | TimerPtr AddDelayTimer(int milliseconds, FUNC_CALLBACK&& func, int count = 1) { 440 | assert(milliseconds > 0); 441 | milliseconds = (std::max)(milliseconds, 1); 442 | auto p = std::make_shared(*this, milliseconds, std::move(func), count); 443 | p->CreateTriggerTime(true); 444 | insert(p); 445 | return p; 446 | } 447 | 448 | std::chrono::system_clock::time_point GetNearestTime() { 449 | auto it = m_timers.begin(); 450 | if (it == m_timers.end()) { 451 | return (std::chrono::system_clock::time_point::max)(); 452 | } else { 453 | return it->first; 454 | } 455 | } 456 | 457 | size_t Update() { 458 | auto time_now = std::chrono::system_clock::now(); 459 | size_t count = 0; 460 | 461 | for (auto it = m_timers.begin(); it != m_timers.end();) { 462 | auto expire_time = it->first; 463 | if (expire_time > time_now) { 464 | break; 465 | } 466 | 467 | // attention: this is a copy, not a reference 468 | auto timer_set = it->second; 469 | it = m_timers.erase(it); 470 | 471 | for (auto p : timer_set) { 472 | p->DoFunc(); 473 | ++count; 474 | } 475 | } 476 | 477 | return count; 478 | } 479 | 480 | private: 481 | void insert(const TimerPtr& p) { 482 | auto t = p->GetTriggerTime(); 483 | auto it = m_timers.find(t); 484 | if (it == m_timers.end()) { 485 | std::set s; 486 | s.insert(p); 487 | m_timers[t] = s; 488 | } else { 489 | std::set& s = it->second; 490 | s.insert(p); 491 | } 492 | } 493 | 494 | void remove(const TimerPtr& p) { 495 | auto t = p->GetTriggerTime(); 496 | auto it = m_timers.find(t); 497 | if (it == m_timers.end()) { 498 | return; 499 | } 500 | 501 | std::set& s = it->second; 502 | s.erase(p); 503 | } 504 | 505 | private: 506 | std::map> m_timers; 507 | }; 508 | 509 | void BaseTimer::Cancel() { 510 | auto self = shared_from_this(); 511 | m_owner.remove(self); 512 | m_canceled = true; 513 | } 514 | 515 | void BaseTimer::DoFunc() { 516 | m_func(); 517 | CreateTriggerTime(true); 518 | 519 | // the timer can be cancelled in m_func() 520 | if (!m_canceled) { 521 | if (m_countLeft == TimerMgr::RUN_FOREVER || m_countLeft > 1) { 522 | if (m_countLeft > 1) { 523 | m_countLeft--; 524 | } 525 | 526 | auto self = shared_from_this(); 527 | m_owner.insert(self); 528 | } 529 | } 530 | } 531 | 532 | } // namespace cron_timer 533 | --------------------------------------------------------------------------------