1. 说明
- 用于定时器事件的内部类,用户看不到这个类,这个类维护了一个定时器列表
- 这个文件有一个TimerQueue类,使用到了EvenLoop,Timer,TimerId,Channel类
- noncopyable
- 定时器使用了timerfd_*系列函数,因为这个系列不会产生信号,在多线程程序中处理信号比较麻烦,这个是当做文件描述符处理,当时间到的时候,这个描述符就变为可读,方便用poll等方式实现
2. 变量
-
一些类型定义
- Entry
- TimerList
- ActiveTimer
- ActiveTimerSet
- TimerQueu需要快速根据当前时间找到已到期的定时器,也要高效的添加和删除Timer,因而可以用二叉搜索树,用map或set
-
loop_
- 所属的EventLoop
-
timerfd_
- const int类型,定时器的文件描述符
-
timerfdChannel_
- timerfd的监听通道
-
timers_
- 按照到期时间排序的Timer*列表
-
activeTimers_
- 和timers_保存的相同的数据,activeTimers_是按照对象地址排序
-
callingExpiredTimers_
- bool类型,是否处于调用处理超时定时器当中,bool类型在Linux下自身就是原子操作
- 在handleRead()函数中使用
-
cancelingTimers_
- 保存的是被取消的定时器
3. 函数
1. 公有方法
- 构造
- 调用了timerfdChannel_.setReadCallback(std::bind(&TimerQueue::handleRead, this)); 意思是当定时器通道可读时,回调handleRead()这个函数
- 调用timerfd_create()生成文件描述符给timerfd赋值
- 调用timerfdChannel_.enableReading();,会调用EventLoop的updateChannel(),从而调用Poller的updateChannel(),把该通道加入poller进行关注
- 析构
- 释放资源
- addTimer(TimerCallback cb,Timestamp when,double interval);
- 添加一个定时器,返回一个TimerId
- 实际是通过loop_调用本类的私有方法addTimerInLoop
- 必须是线程安全的,可以跨线程调用
- cancel(TimerId timerId);
- 传一个TimerId,取消这个定时器
- 实际是通过loop_调用本类的私有方法cancelInLoop
- 以上两个函数并不会直接让用户调用,而是通过EventLoop中使用
2. 私有方法
-
addTimerInLoop(Timer* timer);
- 调用loop_->assertInLoopThread();作用是断言处于IO线程中
- 接着调用insert()插入定时器,但插入的有可能使得最早到期的定时器发生改变,也就是说插入的定时器时间比最早的还早,这时候就会返回true,从而接着调用resetTimerfd(),重置定时器的超时时刻,也就是重新设置最早的定时器,让最早的定时器先触发
-
cancelInLoop(TimerId timerId);
- 如果当前定时器没有处于正在调用处理中,即callingExpiredTimers_为false,这时直接删除这个定时器
- 如果在timers_列表中找不到timerId并且处于callingExpiredTimers_中,可能正在处理这个定时器的回调函数,详见下面handleRead()方法,这时候把该定时器加入到cancelingTimers_ 中,这样的话,在handleRead()处理完后调用reset()方法,就会把这个定时器删掉,不再repeat()
- 这个我看懂后,虽然貌似不是很难想象的操作,但还是奉上一句:大佬大佬!
-
handleRead();
- 先调用getExpired()获得该时刻之前所有的定时器列表,即超时定时器列表
- 设置callingExpiredTimers_为true,即正在处理这些定时器回调,在这个函数结束之前再变为false
- 依次调用这些定时器的run()方法,实际上调用了回调函数
- 最后清除取消列表,调用reset,重启需要重复调用的定时器
-
getExpired(Timestamp now);
- 先调用Set的lower_bound(x)函数,作用是返回第一个值>=x的元素的迭代器iterator,在这里的作用是返回第一个未到期的Timer的迭代器
- 这里的sentry,后一个参数是赋值为最大值,这样比较的时候,如果第一个值相等,第二个值就必然比当前小,调用lower_bound()就满足条件,reinterpret_cast<>用在任意指针或引用类型之间的转换
- 把到期的定时器放到一个新的vector里返回,并从timers_和activeTimers_中删除这些定时器
- 这里直接返回vector,并不会调用拷贝构造,因为会有rvo优化(return value optimization)
-
reset(const std::vector& expired, Timestamp now);
- 重启expired中需要重复的vector,如果需要重复并且没有被取消,重新设定超时时间,插入定时器列表,最后再重新设定最早的定时器
-
insert(Timer* timer);
- 在timers_和activeTimers_中插入这个Timer*对象
- 判断这个timer是否是最早的定时器,返回bool类型,在上面的addTimerInLoop()说明中有说
4. 全局方法
-
createTimerfd()
- 调用timerfd_create()生成文件描述符返回
-
howMuchTimeFromNow(Timestamp when)
- 如名,when比现在晚,计算两个的时间差,返回一个timespec结构体,计算超时时刻与当前时间的时间差
-
readTimerfd(int timerfd, Timestamp now)
- 调用read()读timerfd文件描述符的内容,只读不做其他操作,顺便写日志
- 就是调用read(),清除定时器,避免一直触发
-
resetTimerfd(int timerfd, Timestamp expiration)
- 调用timerfd_settime(),重置定时器的超时时刻