木铎源码剖析—TimerQueue
TimerQueue
成员变量
// FIXME: use unique_ptr<Timer> instead of raw pointers.
// This requires heterogeneous comparison lookup (N3465) from C++14
// so that we can find an T* in a set<unique_ptr<T>>.
typedef std::pair<Timestamp, Timer*> Entry;
typedef std::set<Entry> TimerList;
typedef std::pair<Timer*, int64_t> ActiveTimer;
typedef std::set<ActiveTimer> ActiveTimerSet;
//在事件循环中添加定时器
void addTimerInLoop(Timer* timer);
//在事件循环中取消指定TimerId对应的定时器
void cancelInLoop(TimerId timerId);
// called when timerfd alarms
//当定时器文件描述符触发可读事件时调用的函数
void handleRead();
// move out all expired timers
//获取已过期的定时器列表
std::vector<Entry> getExpired(Timestamp now);
//重新设定定时器队列,将已过期的定时器重新添加到队列中
void reset(const std::vector<Entry>& expired, Timestamp now);
//将定时器插入到定时器列表中
bool insert(Timer* timer);
EventLoop* loop_;
//定时器文件描述符
const int timerfd_;
//用于监听定时器文件描述符的通道
Channel timerfdChannel_;
// Timer list sorted by expiration
// 定时器列表 按照到期事件排序
TimerList timers_;
//活跃的定时器集合 用于处理定时器到期
// for cancel()
ActiveTimerSet activeTimers_;
//用于标识是否正在处理到期的定时器 atomic
bool callingExpiredTimers_;
//用于存储正在取消的定时器集合
ActiveTimerSet cancelingTimers_;
全局方法:createTimerfd() 创建计时器文件描述符
//创建计时器文件描述符
int createTimerfd(){
//创建并操作计时器 该计时器通过文件描述符传递计时器过期通知
//CLOCK_MONOTONIC 表示使用系统的单调时钟 TFD_NONBLOCK:非阻塞 TFD_CLOEXEC:在执行exec调用时自动关闭文件描述符
int timerfd = ::timerfd_create(CLOCK_MONOTONIC,
TFD_NONBLOCK | TFD_CLOEXEC);
if (timerfd < 0)
{
LOG_SYSFATAL << "Failed in timerfd_create";
}
return timerfd;
}
全局方法:howMuchTimeFromNow用于计算从当前事件到指定时间点的事件间隔,返回timespec结构体
struct timespec howMuchTimeFromNow(Timestamp when)
{
int64_t microseconds = when.microSecondsSinceEpoch()
- Timestamp::now().microSecondsSinceEpoch();
if (microseconds < 100)
{
microseconds = 100;
}
struct timespec ts;
ts.tv_sec = static_cast<time_t>(
microseconds / Timestamp::kMicroSecondsPerSecond);
ts.tv_nsec = static_cast<long>(
(microseconds % Timestamp::kMicroSecondsPerSecond) * 1000);
return ts;
}
全局方法:readTimerfd获取在文件描述符中存储的到期次数
void readTimerfd(int timerfd, Timestamp now)
{
uint64_t howmany;
ssize_t n = ::read(timerfd, &howmany, sizeof howmany);
LOG_TRACE << "TimerQueue::handleRead() " << howmany << " at " << now.toString();
if (n != sizeof howmany)
{
LOG_ERROR << "TimerQueue::handleRead() reads " << n << " bytes instead of 8";
}
}
全局方法:resetTimerfd
通过timerfd_settime函数重新设置定时器文件描述符
该函数的目的是通过重新设置定时器的到期时间,唤醒事件循环的执行
void resetTimerfd(int timerfd, Timestamp expiration)
{
// wake up loop by timerfd_settime()
struct itimerspec newValue;
struct itimerspec oldValue;
memZero(&newValue, sizeof newValue);
memZero(&oldValue, sizeof oldValue);
newValue.it_value = howMuchTimeFromNow(expiration);
//这个函数会将定时器文件描述符的到期时间设置为newValue,通过oldvalue返回之前的到期时间
int ret = ::timerfd_settime(timerfd, 0, &newValue, &oldValue);
if (ret)
{
LOG_SYSERR << "timerfd_settime()";
}
}
构造函数需要完成定时器文件描述符的创建,以及各类回调函数的绑定
TimerQueue::TimerQueue(EventLoop* loop)
: loop_(loop),
timerfd_(createTimerfd()),
timerfdChannel_(loop, timerfd_),
timers_(),
callingExpiredTimers_(false)
{
timerfdChannel_.setReadCallback(
std::bind(&TimerQueue::handleRead, this));
// we are always reading the timerfd, we disarm it with timerfd_settime.
timerfdChannel_.enableReading();
}
下面展示如何添加定时器,并放入事件循环器中,共涉及到三个函数如下所示;
首先构建timer结构体,实际会将执行函数放到循环器中,执行插入,并判断此定时器是否是最早改变的,如果是的话,则会重置定时器的到期时间;返回的是一个TimerId的结构体,存储了定时器,及序列号。
TimerId TimerQueue::addTimer(TimerCallback cb,
Timestamp when,
double interval)
{
Timer* timer = new Timer(std::move(cb), when, interval);
loop_->runInLoop(
std::bind(&TimerQueue::addTimerInLoop, this, timer));
return TimerId(timer, timer->sequence());
}
void TimerQueue::addTimerInLoop(Timer* timer)
{
loop_->assertInLoopThread();
bool earliestChanged = insert(timer);
if (earliestChanged)
{
resetTimerfd(timerfd_, timer->expiration());
}
}
bool TimerQueue::insert(Timer* timer)
{
loop_->assertInLoopThread();
assert(timers_.size() == activeTimers_.size());
bool earliestChanged = false;
Timestamp when = timer->expiration();
TimerList::iterator it = timers_.begin();
if (it == timers_.end() || when < it->first)
{
earliestChanged = true;
}
{
std::pair<TimerList::iterator, bool> result
= timers_.insert(Entry(when, timer));
assert(result.second); (void)result;
}
{
std::pair<ActiveTimerSet::iterator, bool> result
= activeTimers_.insert(ActiveTimer(timer, timer->sequence()));
assert(result.second); (void)result;
}
assert(timers_.size() == activeTimers_.size());
return earliestChanged;
}
取消某一个定时器
void TimerQueue::cancel(TimerId timerId)
{
loop_->runInLoop(
std::bind(&TimerQueue::cancelInLoop, this, timerId));
}
void TimerQueue::cancelInLoop(TimerId timerId)
{
loop_->assertInLoopThread();
assert(timers_.size() == activeTimers_.size());
ActiveTimer timer(timerId.timer_, timerId.sequence_);
ActiveTimerSet::iterator it = activeTimers_.find(timer);
if (it != activeTimers_.end())
{
size_t n = timers_.erase(Entry(it->first->expiration(), it->first));
assert(n == 1); (void)n;
delete it->first; // FIXME: no delete please
activeTimers_.erase(it);
}
else if (callingExpiredTimers_)
{
cancelingTimers_.insert(timer);
}
assert(timers_.size() == activeTimers_.size());
}
handleRead成员方法构建
void TimerQueue::handleRead()
{
loop_->assertInLoopThread();
Timestamp now(Timestamp::now());
readTimerfd(timerfd_, now);
//获取过期的时间列表
std::vector<Entry> expired = getExpired(now);
//设置标志位 并将容器置为空
callingExpiredTimers_ = true;
cancelingTimers_.clear();
// safe to callback outside critical section
//执行定时器回调函数
for (const Entry& it : expired)
{
it.second->run();
}
callingExpiredTimers_ = false;
reset(expired, now);
}
std::vector<TimerQueue::Entry> TimerQueue::getExpired(Timestamp now)
{
assert(timers_.size() == activeTimers_.size());
std::vector<Entry> expired;
Entry sentry(now, reinterpret_cast<Timer*>(UINTPTR_MAX));
//寻找第一个大于或等于sentry的迭代器
TimerList::iterator end = timers_.lower_bound(sentry);
assert(end == timers_.end() || now < end->first);
std::copy(timers_.begin(), end, back_inserter(expired));
timers_.erase(timers_.begin(), end);
for (const Entry& it : expired)
{
ActiveTimer timer(it.second, it.second->sequence());
size_t n = activeTimers_.erase(timer);
assert(n == 1); (void)n;
}
assert(timers_.size() == activeTimers_.size());
return expired;
}
void TimerQueue::reset(const std::vector<Entry>& expired, Timestamp now)
{
Timestamp nextExpire;
for (const Entry& it : expired)
{
ActiveTimer timer(it.second, it.second->sequence());
if (it.second->repeat()
&& cancelingTimers_.find(timer) == cancelingTimers_.end())
{
it.second->restart(now);
insert(it.second);
}
else
{
// FIXME move to a free list
delete it.second; // FIXME: no delete please
}
}
if (!timers_.empty())
{
nextExpire = timers_.begin()->second->expiration();
}
if (nextExpire.valid())
{
resetTimerfd(timerfd_, nextExpire);
}
}
总结:该定时器队列中用到了最关键的结构体set,底层是基于平衡二叉树实现的 ,可以自动排列插入的数据的大小,模型是从小到大的,在该类中维护了一个关于定时器的文件描述符,并且提供了添加定时器,移除定时器这两个最为关键的方法,为了可以实现长期循环,这两个函数的实际实现均放到了EventLoop中,对于添加定时器,除了要将定时器插入set外还需要判断该定时器是否是最早会触发的,如果是那么需要充值文件描述符的触发时间;删除定时器只需要判断该定时器是否在set中,来进行删除,值得注意的是,我们发现在定时器的存储上采用了两个对象进行存储,一个是以<Timestamp,Timer>进行存储,另一个则是通过<Timer,int64_t>来存储的,int64_t是定时器内部的一个序列号,他是由静态原子变量进行维护的,作用是每创建一个定时器该序列号会自定加1,这样每个定时器的序列号都是不一样的,创建两个对象存储数据,同样可以保证在执行数据操作时的一致性;而对于定时器文件描述符到期后,会触发回调函数handleRead,通过该函数可以获取到期的定时器列表,当然这些定时器都会被从set中清理出来,在重置后会添加进去。

浙公网安备 33010602011771号