这是一个使用CronTab表达式的定时器,可以在指定时间点触发定时器事件,也可以在一段时间之后触发定时器事件。
编号 | 内容 | 状态 |
---|---|---|
1 | 参考一下 https://www.zhihu.com/question/32251997/answer/1899420964?content_id=1379404441725026304&type=zvideo 优化定时器的触发逻辑 | 20210525,xinyong已完成 |
2 | 在Update函数中查找最近的定时器,然后可以等待较长的时间,降低执行次数,提升效率 | 20210525,xinyong已完成 |
3 | 日志的时间表示从毫秒改成微秒,让误差更有说服力 | 20210611, xinyong已完成 |
5 | 支持在定时器处理函数中取消自己 | 20210721, xinyong已完成 |
6 | AddTimer的返回值应该由一个unique_ptr来接,由返回值控制其生命周期 | |
7 | 修复未来的CronTimer无法正确触发的bug,感谢RosenYin发现bug | 20231111,xinyong已修正 |
- 对时间的表达能力强,CronTab表达式已经在Linux平台上广泛使用,可以参考 https://www.bejson.com/othertools/cron/?ivk_sa=1024320u 生成表达式,本组件对这个表达式少有修改,具体可以参考示例代码。
- 使用方便,一个头文件搞定一切,拷贝过去就可以使用,不依赖第三方库,Windows、Centos、Ubuntu、Mac都可以运行。一行代码添加一个定时器,可传入成员函数,携带自定义参数。
- 精度高、误差不累积,实测误差小于1毫秒。
- 性能好,对于定时器内的对象个数,时间判断的时间复杂度做到了O(log(n)),可以支持海量的定时器对象。
cron_timer::TimerMgr mgr;
mgr.AddTimer("* * * * * *", [](void) {
// 每秒钟都会执行一次
Log("1 second cron timer hit");
});
mgr.AddTimer("0/3 * * * * *", [](void) {
// 从0秒开始,每3秒钟执行一次
Log("3 second cron timer hit");
});
mgr.AddTimer("0 * * * * *", [](void) {
// 1分钟执行一次(每次都在0秒的时候执行)的定时器
Log("1 minute cron timer hit");
});
mgr.AddTimer("15;30;33 * * * * *", [](void) {
// 指定时间(15秒、30秒和33秒)点都会执行一次
Log("cron timer hit at 15s or 30s or 33s");
});
mgr.AddTimer("40-50 * * * * *", [](void) {
// 指定时间段(40到50内的每一秒)执行的定时器
Log("cron timer hit between 40s to 50s");
});
std::weak_ptr<cron_timer::BaseTimer> timer = mgr.AddDelayTimer(3000, [](void) {
// 3秒钟之后执行
Log("3 second delay timer hit");
});
// 可以在执行之前取消
if (auto ptr = timer.lock(); ptr != nullptr) {
ptr->Cancel();
}
mgr.AddDelayTimer(
10000,
[](void) {
// 每10秒钟执行一次,总共执行3次
Log("10 second delay timer hit");
},
3);
Log("10 second delay timer added");
while (!_shutDown) {
auto nearest_timer =
(std::min)(std::chrono::system_clock::now() + std::chrono::milliseconds(500), mgr.GetNearestTime());
std::this_thread::sleep_until(nearest_timer);
mgr.Update();
}
精度有多高,误差小于1毫秒,数据说话:
2021-06-11 22:25:31.017870 10 second delay timer added
2021-06-11 22:25:31.017937 1 second cron timer hit
2021-06-11 22:25:32.000337 1 second cron timer hit
2021-06-11 22:25:33.000450 3 second cron timer hit
2021-06-11 22:25:33.000508 cron timer hit at 15s or 30s or 33s
2021-06-11 22:25:33.000534 1 second cron timer hit
2021-06-11 22:25:34.000368 1 second cron timer hit
2021-06-11 22:25:35.000398 1 second cron timer hit
2021-06-11 22:25:36.000455 3 second cron timer hit
2021-06-11 22:25:36.000512 1 second cron timer hit
2021-06-11 22:25:37.000385 1 second cron timer hit
2021-06-11 22:25:38.000349 1 second cron timer hit
2021-06-11 22:25:39.000380 3 second cron timer hit
2021-06-11 22:25:39.000449 1 second cron timer hit
2021-06-11 22:25:40.000544 cron timer hit between 40s to 50s
2021-06-11 22:25:40.000603 1 second cron timer hit
2021-06-11 22:25:41.000307 cron timer hit between 40s to 50s
2021-06-11 22:25:41.000361 1 second cron timer hit
2021-06-11 22:25:41.017942 10 second delay timer hit
2021-06-11 22:25:42.000431 3 second cron timer hit
2021-06-11 22:25:42.000488 cron timer hit between 40s to 50s
2021-06-11 22:25:42.000525 1 second cron timer hit
2021-06-11 22:25:43.000369 cron timer hit between 40s to 50s
2021-06-11 22:25:43.000425 1 second cron timer hit
2021-06-11 22:25:44.000370 cron timer hit between 40s to 50s
2021-06-11 22:25:44.000424 1 second cron timer hit
2021-06-11 22:25:45.000447 3 second cron timer hit
2021-06-11 22:25:45.000505 cron timer hit between 40s to 50s
2021-06-11 22:25:45.000532 1 second cron timer hit
2021-06-11 22:25:46.000357 cron timer hit between 40s to 50s
2021-06-11 22:25:46.000411 1 second cron timer hit
2021-06-11 22:25:47.000355 cron timer hit between 40s to 50s
2021-06-11 22:25:47.000408 1 second cron timer hit
2021-06-11 22:25:48.000438 3 second cron timer hit
2021-06-11 22:25:48.000496 cron timer hit between 40s to 50s
2021-06-11 22:25:48.000522 1 second cron timer hit
1.为什么没有使用时间轮?
答:时间轮定时器作为延时触发是很合适的,时间复杂度都是O(1)的,但是对于在固定时间点触发的定时器,我觉得会有累积误差,我暂时还没找到解决的方法,所以使用了红黑树,红黑树的时间复杂度是O(log(N))的,性能也不错。
2.为什么误差能做到1毫秒以下?
答:对于定时器组件,需要提供一个“获取最近即将触发的时间”这样一个接口,有了这样一个接口,在消息队列中就可以做到堆这个时间的精确等待,这个等待操作不是简单的sleep几毫秒,是使用了CPU硬件提供的计时器功能,超时的时候触发一个中断。当然重要的是红黑树LogN的时间复杂度在发挥作用。
如果您觉得不错,感谢Star,如果您觉得有问题,欢迎提issue