系列文章目录
《ZLToolKit源码学习笔记》(1)VS2019源码编译
《ZLToolKit源码学习笔记》(2)工具模块之日志功能分析
《ZLToolKit源码学习笔记》(3)工具模块之终端命令解析
《ZLToolKit源码学习笔记》(4)工具模块之消息广播器
《ZLToolKit源码学习笔记》(6)线程模块之整体框架概述
《ZLToolKit源码学习笔记》(7)线程模块之线程池组件:任务队列与线程组
《ZLToolKit源码学习笔记》(8)线程模块之线程负载计算器
《ZLToolKit源码学习笔记》(9)线程模块之任务执行器
《ZLToolKit源码学习笔记》(11)线程模块之工作线程池WorkThreadPool
《ZLToolKit源码学习笔记》(12)事件轮询模块之整体框架概述
《ZLToolKit源码学习笔记》(13)事件轮询模块之管道的简单封装
《ZLToolKit源码学习笔记》(14)事件轮询模块之定时器(本文)
《ZLToolKit源码学习笔记》(15)事件轮询模块之事件轮询器EventPoller
《ZLToolKit源码学习笔记》(16)网络模块之整体框架概述
《ZLToolKit源码学习笔记》(17)网络模块之基础接口封装类SockUtil
《ZLToolKit源码学习笔记》(18)网络模块之Buffer缓存
《ZLToolKit源码学习笔记》(19)网络模块之套接字封装
《ZLToolKit源码学习笔记》(20)网络模块之TcpServer
《ZLToolKit源码学习笔记》(21)网络模块之TcpClient与Session
《ZLToolKit源码学习笔记》(22)网络模块之UdpServer
前言
任务的定时执行在服务器编程中经常用到,本节学习下ZLToolKit封装的定时器。Timer.h和Timer.cpp。
目录
4.3、getMinDelay-获取最近待执行任务的间隔时间
一、概述
ZLToolKit的定时器是通过线程+循环判断任务的执行时间是否到达来实现,添加任务时,记录该任务下一次执行时间点,然后把定时任务插入到队列中,在线程runLoop中,检测队列中是否有任务已经到达执行时间点,有则立即执行该任务,否则,把最近将要执行的任务的时间点作为线程的休眠时间。以此循环,线程下一次唤醒后重复执行上述操作。
以上,定时器功能主要涉及到三个类。
TaskCancelableImp,对任务的封装,支持任务的取消;
EventPoller,事件轮询器,管理定时任务,在线程中循环判任务是否到达执行时间,并执行已经到达时间的任务。对于需要重复定时执行的任务,在本次执行完成后,会重新计算任务的执行时间点,并添加到延时任务队列中。
Timer,定时器封装类,将任务(TaskCancelableImp)和事件轮询器(EventPoller)关联起来。Timer对象析构时,会取消任务。
二、TaskCancelableImp
该类在线程池一节中已经学习过,这里不做分析。
《ZLToolKit源码学习笔记》(10)线程模块之线程池_秦时小-优快云博客
三、Timer
该类只有构造函数和析构函数。在构造函数中,将定时任务添加到事件轮询器中进行管理,在析构函数中,取消任务。
3.1、构造函数
Timer::Timer(float second,
const function<bool()> &cb,
const EventPoller::Ptr &poller,
bool continueWhenException) {
_poller = poller;
if(!_poller){
_poller = EventPollerPool::Instance().getPoller();
}
_tag = _poller->doDelayTask((uint64_t)(second * 1000), [cb, second , continueWhenException]() {
try {
if (cb()) {
//重复的任务
return (uint64_t) (1000 * second);
}
//该任务不再重复
return (uint64_t) 0;
}catch (std::exception &ex){
ErrorL << "执行定时器任务捕获到异常:" << ex.what();
return continueWhenException ? (uint64_t) (1000 * second) : 0;
}
});
}
可以看到,second是任务的执行间隔,cb是任务回调,poller是事件轮询器,continueWhenException确定在发生异常时定时任务是否还继续执行。
通过调用eventPoller的doDelayTask接口将任务添加到事件轮询器中进行管理,cb的返回值决定定时任务是否继续下一轮执行,true则继续,false则取消。
3.2、析构函数
析构函数中,取消了任务。
Timer::~Timer() {
auto tag = _tag.lock();
if(tag){
tag->cancel();
}
}
四、EventPoller
该类主要是对事件的管理。支持的事件类型较多,这里仅关注与定时器相关的。
定时任务统一添加在multimap中管理。key是任务的下一次执行时间点,value是任务。自动根据任务执行时间点进行升序排列,遍历时,第一条即就是会最先到达执行时间的任务。
multimap<uint64_t, DelayTask::Ptr> _delay_task_map;
这里看一下任务的类型DelayTask,可以看到类型实际上就是TaskCancelableImp的一个特例化。
using DelayTask = TaskCancelableImp<uint64_t(void)>;
这部分单功能测试可以参见ZLToolKit\tests\test_delayTask.cpp文件。
4.1、doDelayTask-添加延时任务
DelayTask::Ptr EventPoller::doDelayTask(uint64_t delayMS, function<uint64_t()> task) {
DelayTask::Ptr ret = std::make_shared<DelayTask>(std::move(task));
auto time_line = getCurrentMillisecond() + delayMS;
async_first([time_line, ret, this]() {
//异步执行的目的是刷新select或epoll的休眠时间
_delay_task_map.emplace(time_line, ret);
});
return ret;
}
任务的执行时间 = 当前时间 + 定时间隔。使用async_first来异步的执行任务的添加操作,通过内置的管道事件,先唤醒休眠中的runLoop线程,将任务添加到_delay_task_map中。
4.2、flushDelayTask-刷新延时任务
uint64_t EventPoller::flushDelayTask(uint64_t now_time) {
decltype(_delay_task_map) task_copy;
task_copy.swap(_delay_task_map);
for (auto it = task_copy.begin(); it != task_copy.end() && it->first <= now_time; it = task_copy.erase(it)) {
//已到期的任务
try {
auto next_delay = (*(it->second))();
if (next_delay) {
//可重复任务,更新时间截止线
_delay_task_map.emplace(next_delay + now_time, std::move(it->second));
}
} catch (std::exception &ex) {
ErrorL << "EventPoller执行延时任务捕获到异常:" << ex.what();
}
}
task_copy.insert(_delay_task_map.begin(), _delay_task_map.end());
task_copy.swap(_delay_task_map);
auto it = _delay_task_map.begin();
if (it == _delay_task_map.end()) {
//没有剩余的定时器了
return 0;
}
//最近一个定时器的执行延时
return it->first - now_time;
}
执行已经到期的任务,并返回最近一个定时器的执行延时。
4.3、getMinDelay-获取最近待执行任务的间隔时间
uint64_t EventPoller::getMinDelay() {
auto it = _delay_task_map.begin();
if (it == _delay_task_map.end()) {
//没有剩余的定时器了
return 0;
}
auto now = getCurrentMillisecond();
if (it->first > now) {
//所有任务尚未到期
return it->first - now;
}
//执行已到期的任务并刷新休眠延时
return flushDelayTask(now);
}
获取最近将要执行的任务距离当前时间的间隔。runLoop线程再次休眠前,调用getMinDelay函数更新自己的休眠时间为距离下一个定时任务的执行间隔时间。比如,当前时间08:29:58,最近的任务在08:30:00时会执行,那么休眠时间就是2秒。
flushDelayTask中,会执行已经到达执行时间的任务。
五、测试
测试程序见ZLToolKit\tests\test_timer.cpp,演示了可重复执行、不可重复执行、以及发生异常的定时任务的使用。