定时器是基于EventLoop
所有定时器都注册在一个定时器IO上,定时器IO在到达指定时间后发生可读事件,EventLoop读该链接并调用当前已过期的所有定时器的函数,这些函数都属于EventLoop中pendingFunctors_
首先看定时器Timer:
成员:
const TimerCallback callback_; //绑定的回调函数
Timestamp expiration_; //指定的时间戳
const double interval_; //循环次数
const bool repeat_; //是否重复
const int64_t sequence_; //定时器ID
static AtomicInt64 s_numCreated_; //定时器个数
只定义了一个函数,作用是如果重复定时器的话,在执行了相关回调函数后将添加到定时器队列中等待下一次触发
void Timer::restart(Timestamp now)
{
if (repeat_)
{
expiration_ = addTime(now, interval_);
}
else
{
expiration_ = Timestamp::invalid();
}
}
TimerID:
这是一个封装定时器和其ID的类
TimerQueue:
成员:
首先要知道set数据结构是有序的,按照从小到大排列,如果是指定成员为pair时,排序先按照S大小,再按照X大小排列
// so that we can find an T* in a set>.
typedef std::pair Entry;
typedef std::set TimerList;
typedef std::pair ActiveTimer;
typedef std::set ActiveTimerSet;
void addTimerInLoop(Timer* timer);
void cancelInLoop(TimerId timerId);
// called when timerfd alarms
void handleRead();
// move out all expired timers
std::vector getExpired(Timestamp now);
void reset(const std::vector& expired, Timestamp now);
bool insert(Timer* timer);
EventLoop* loop_; //线程绑定的loop对象
const int timerfd_; //定时器IO描述符
Channel timerfdChannel_; //定时器链接
// Timer list sorted by expiration
TimerList timers_; //默认按照时间排序
// for cancel()
ActiveTimerSet activeTimers_; //默认按照地址排序
bool callingExpiredTimers_; /* atomic */
ActiveTimerSet cancelingTimers_; //需要取消的定时器
具体实现:
int createTimerfd() //创建定时器描述符
struct timespec howMuchTimeFromNow(Timestamp when) //Timestamp转换为timespec
void readTimerfd(int timerfd, Timestamp now) //从定时器IO中读取信息,作为回调函数,读后防止水平触发
void resetTimerfd(int timerfd, Timestamp expiration) //将定时器IO重新设置时间
TimerId TimerQueue::addTimer(const TimerCallback& cb, //非本线程调用
Timestamp when,
double interval)
{
Timer* timer = new Timer(cb, when, interval);
//添加到IO线程中等待被调用,线程安全的不需要锁
loop_->runInLoop(
boost::bind(&TimerQueue::addTimerInLoop, this, timer));
return TimerId(timer, timer->sequence());
}
先看runInLoop函数:
void EventLoop::runInLoop(Functor&& cb)
{
if (isInLoopThread()) //判断是否在本线程,如果是则直接调用函数
{
cb();
}
else
{
queueInLoop(std::move(cb));
}
}
如果不是IO线程调用的话,需要添加到pendingFunctors_中,等待统一的调用,并且如果是其他线程就要唤醒,或者正在处于统一的调用中时需要唤醒,因为此次添加的函数在此时不会立即执行,因为有两个队列,一个是pendingFunctors_,另一个是执行中pendingFunctors_的副本,添加的函数在pendingFunctors中,其副本不会添加,执行的是副本中的函数,具体原因后面有
void EventLoop::queueInLoop(Functor&& cb)
{
{
MutexLockGuard lock(mutex_);
pendingFunctors_.push_back(std::move(cb)); // emplace_back
}
if (!isInLoopThread() || callingPendingFunctors_)
{
wakeup();
}
}
涉及到定时器的操作添加删除都是由创建定时器的那个IO线程进行,而其他线程需要添加定时器时,需要将其设置为事件使得IO线程进行添加删除操作
void TimerQueue::addTimerInLoop(Timer* timer) //在本线程中调用
{
loop_->assertInLoopThread();
bool earliestChanged = insert(timer); //增加到定时器集合和活跃的定时器集合中,
//如果时间比之前已有定时器短则返回True
if (earliestChanged)
{
resetTimerfd(timerfd_, timer->expiration()); //当前时间需要修改时,更改定时器IO的时间
}
}
删除:
void TimerQueue::cancel(TimerId timerId)
{
loop_->runInLoop(
boost::bind(&TimerQueue::cancelInLoop, this, timerId));
}
void TimerQueue::cancelInLoop(TimerId timerId)
{
loop_->assertInLoopThread();
assert(timers_.size() == activeTimers_.size());
ActiveTimer timer(timerId.timer_, timerId.sequence_); //转换为Timer
ActiveTimerSet::iterator it = activeTimers_.find(timer); //查找到活跃定时器指针
if (it != activeTimers_.end())
{
size_t n = timers_.erase(Entry(it->first->expiration(), it->first)); //删除定时器集合中的Timer
assert(n == 1); (void)n;
delete it->first; // FIXME: no delete please 释放Timer
activeTimers_.erase(it); //删除活跃定时器中Timer
}
else if (callingExpiredTimers_) //正在调用过期定时器时,添加进cancelingTimers
{
cancelingTimers_.insert(timer);
}
assert(timers_.size() == activeTimers_.size());
}
定时器IO回调函数:
void TimerQueue::handleRead() //定时器回调函数
{
loop_->assertInLoopThread();
Timestamp now(Timestamp::now());
readTimerfd(timerfd_, now); //读取事件,如果不读取由于Epoll水平触发会导致一直有IO事件产生
std::vector expired = getExpired(now); //获得过期的定时器
callingExpiredTimers_ = true; //设置当前状态为调用定时器函数中
cancelingTimers_.clear(); //清空删除的定时器
// safe to callback outside critical section
for (std::vector::iterator it = expired.begin();
it != expired.end(); ++it)
{
it->second->run(); //执行函数
}
callingExpiredTimers_ = false;
reset(expired, now); //如果有时间间隔的定时器需要重新添加进定时器
}
std::vector TimerQueue::getExpired(Timestamp now) //获得已过期的定时器
{
assert(timers_.size() == activeTimers_.size());
std::vector expired;
Entry sentry(now, reinterpret_cast(UINTPTR_MAX)); //新建一个当前时间定时器,
//用UINTPTR_MAX原因是默认排序
//如果时间相等则地址大小排序,取最大防止漏掉定时器
TimerList::iterator end = timers_.lower_bound(sentry); //使用指针找到当前时间最后一个定时器(第一个>=sentry的定时器)
assert(end == timers_.end() || now < end->first);
std::copy(timers_.begin(), end, back_inserter(expired)); //将开始到现在的定时器复制到expired返回,end之前不包括end
timers_.erase(timers_.begin(), end); //删除这一范围定时器
for (std::vector::iterator it = expired.begin();
it != expired.end(); ++it)
{
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;
}
现在看EventLoop中事件处理函数(一般称为计算函数相对于IO处理函数):
这是Loop的调用,其中前面都是eventHanding状态(IO处理)
之前讲过,现在看最后一个调用doPendingFunctors
while (!quit_)
{
activeChannels_.clear(); //清理活跃中的IO通道
pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_); //等待Poller将活跃的IO通道传送过来
++iteration_;
if (Logger::logLevel() <= Logger::TRACE)
{
printActiveChannels(); //输出通道
}
// TODO sort channel by priority
eventHandling_ = true; //处理事件状态
for (ChannelList::iterator it = activeChannels_.begin(); //遍历每个IO通道处理相关事件
it != activeChannels_.end(); ++it)
{
currentActiveChannel_ = *it;
currentActiveChannel_->handleEvent(pollReturnTime_);
}
currentActiveChannel_ = NULL;
eventHandling_ = false;
doPendingFunctors(); //调用任务处理函数
}
这是callingPendingFunctors_(计算处理),因为服务器一般分为两个过程,IO处理,cpu处理,前者用于IO相关事件处理,后者用于计算时间比较长的相关事件处理,两者分开更适合分布式网络的一般情况(高IO,低延迟,快速计算,分布集群等等)
void EventLoop::doPendingFunctors()
{
std::vector functors; //任务副本,防止任务调用queueInLoop陷入死循环
callingPendingFunctors_ = true; //设置当前Loop处于任务处理状态
{
MutexLockGuard lock(mutex_); //加锁取出任务到副本,并清空任务队列
functors.swap(pendingFunctors_); //之所以局部加锁是因为防止任务调用queueInLoop陷入死锁
}
for (size_t i = 0; i < functors.size(); ++i)
{
functors[i]();
}
callingPendingFunctors_ = false;
}
这里pendingFuntors还有个副本,是因为
1.一旦调用中还有添加事件到队列中不会引起死循环
2.lock在swap范围外包裹了循环的话,调用可能引起加锁解锁,很可能发生死锁状态