Muduo分析及總結(一)定時器(TimerQueue、TimerId、Timer)

一、類關係圖

在這裏插入圖片描述
TimerId :表示一個定時器的整體,有定時器和序列號。
Timer:就是一個定時器的封裝。
TimerQueue:定時器的處理流程封裝。

二、時序圖

在這裏插入圖片描述

三、定時器的使用

TimeQueue被封裝在EventLoop中,定時器的使用要通過EventLoop

/在時間戳爲time的時間執行,0.0表示一次性不重複
TimerId EventLoop::runAt(const Timestamp& time, const TimerCallback& cb)
{
  return timerQueue_->addTimer(cb, time, 0.0);  
}
 
//延遲delay時間執行的定時器
TimerId EventLoop::runAfter(double delay, const TimerCallback& cb)
{
  Timestamp time(addTime(Timestamp::now(), delay));  //合成一個時間戳
  return runAt(time, cb);
}
 
//間隔性的定時器,起始就是重複定時器,間隔interval需要大於0
TimerId EventLoop::runEvery(double interval, const TimerCallback& cb)
{
  Timestamp time(addTime(Timestamp::now(), interval));
  return timerQueue_->addTimer(cb, time, interval);
}
//直接調用timerQueue的cancle
void EventLoop::cancel(TimerId timerId)
{
  return timerQueue_->cancel(timerId);  
)

這裏講解下添加定時器,定時器到期,以及取消定時器的流程。

3.1 添加定時器

TimerQueue->addTimer:

//cb 定時器到期執行函數,when 定時器到期時間,interval非零表示重複定時器
TimerId TimerQueue::addTimer(const TimerCallback& cb,
                             Timestamp when,
                             double interval)
{
 //實例化一個定期器類
  Timer* timer = new Timer(cb, when, interval);
  //將addTimerInLoop方法放到EventLoop中執行,若用戶在當前IO線程,回調則同步執行,否則將方法加入到隊列,
  //待IO線程被喚醒之後再來調用此回調。
  loop_->runInLoop(boost::bind(&TimerQueue::addTimerInLoop, this, timer));
  //實例化一個定時器和序列號封裝的TimerId進行返回,用於用戶取消定時器
  return TimerId(timer, timer->sequence());
}

TimerQueue->addTimerInLoop:

void TimerQueue::addTimerInLoop(Timer* timer)
{
  //判斷是否在當前線程中
  loop_->assertInLoopThread();
  //插入一個定時器,並返回新添加的定時器是不是比隊列裏已存在的所有定時器過期時間還早
  bool earliestChanged = insert(timer);

  if (earliestChanged)
  {
    //如果新入隊的定時器是隊列裏最早的,從新設置下系統定時器到期觸發時間
    resetTimerfd(timerfd_, timer->expiration());
  }
}

TimerQueue->insert:

bool TimerQueue::insert(Timer* timer)
{
 //檢查是否在當前線程
  loop_->assertInLoopThread();
  //timers_和activeTimers_存着同樣的定時器列表,個數是一樣的
  assert(timers_.size() == activeTimers_.size());
  //timers_是按過期時間有序排列的,最早到期的在前面
  //新插入的時間和隊列中最早到期時間比,判斷新插入時間是否更早
  bool earliestChanged = false;
  Timestamp when = timer->expiration();
  TimerList::iterator it = timers_.begin();
  if (it == timers_.end() || when < it->first)
  {
    earliestChanged = true;
  }
  //將時間保存入隊,std::set自動保存有序
  {
    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;
}

Muduo->Net->Detail->resetTimerfd:

//此方法很簡單,就是調用linux系統函數,重新設置下到期時間
void resetTimerfd(int timerfd, Timestamp expiration)
{
  // wake up loop by timerfd_settime()
  struct itimerspec newValue;
  struct itimerspec oldValue;
  bzero(&newValue, sizeof newValue);
  bzero(&oldValue, sizeof oldValue);
  newValue.it_value = howMuchTimeFromNow(expiration);
  int ret = ::timerfd_settime(timerfd, 0, &newValue, &oldValue);
  if (ret)
  {
    LOG_SYSERR << "timerfd_settime()";
  }
}

3.2 定時器到期

TimerQueue->handleRead:

//TimerQueue實例化時設置好定時器回調。這裏用到了Muduo通道Channel方法。
TimerQueue::TimerQueue(EventLoop* loop)
  : loop_(loop),
    timerfd_(createTimerfd()),
    timerfdChannel_(loop, timerfd_),
    timers_(),
    callingExpiredTimers_(false)
{
  timerfdChannel_.setReadCallback(
      boost::bind(&TimerQueue::handleRead, this));
  // we are always reading the timerfd, we disarm it with timerfd_settime.
  timerfdChannel_.enableReading();
}
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 (std::vector<Entry>::iterator it = expired.begin();
      it != expired.end(); ++it)
  {
    it->second->run();
  }
  callingExpiredTimers_ = false;
 //重置過期定時器狀態,如果是重複執行定時器就再入隊,否則刪除
  reset(expired, now);
}

TimerQueue->getExpired:

//獲取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));
  //這裏用到了std::set::lower_bound(尋找第一個大於等於Value的值),
  //這裏的Timer*是UINTPTR_MAX,所以返回的是一個大於Value的值,即第一個未到期的Timer的迭代器
  TimerList::iterator end = timers_.lower_bound(sentry);
  //end是一個大於sentry的值,所以是<判斷。
  assert(end == timers_.end() || now < end->first);
  //拷貝出過期的定時器
  std::copy(timers_.begin(), end, back_inserter(expired));
  //從已有隊列中清楚
  timers_.erase(timers_.begin(), end);

  for (std::vector<Entry>::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;
}

這裏getExpired返回的是一個列表,而我們設置系統過期時間是定時器隊列裏面最早的時間,如果所有的定時器時間都不一樣,
則getExpired返回的列表中永遠都只有一個元素。但是我們可以設置多個定時器都具有相同的過期時間,那麼getExpired返回的列表中是具有相同過期時間的定時器。

TimerQueue->reset:

void TimerQueue::reset(const std::vector<Entry>& expired, Timestamp now)
{
  Timestamp nextExpire;

  for (std::vector<Entry>::const_iterator it = expired.begin();
      it != expired.end(); ++it)
  {
    ActiveTimer timer(it->second, it->second->sequence());
    if (it->second->repeat()
        && cancelingTimers_.find(timer) == cancelingTimers_.end())
    {
     //1、定時器是重複定時器,2、取消定時器隊列中無此定時器。
     //則此定時器再入隊
      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);
  }
}

3.3 取消定時器

TimerQueue->cancel:

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_);
  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_)
  {
    //callingExpiredTimers_爲true表示過期定時觸發,正在handleRead中處理過期時間
    //將要取消的定時器放入取消定時器隊列,TimerQueue->reset會用到。
    cancelingTimers_.insert(timer);
  }
  assert(timers_.size() == activeTimers_.size());
}

TimerQueue->cancelInLoop方法中cancelingTimers_callingExpiredTimers_是爲了應對“自注銷”這種情況。
舉例:

muduo::EventLoop*g_loop;
muduo::TimerId toCancel;

void cancelSelf()
{
	print("cancelSelf()");
	g_loop->cancel(toCancel);
}

int main()
{
	muduo::EventLoop loop;
	g_loop = &loop;

	toCancel = loop.runEvery(5, cancelSelf);
	loop.loop()
}

當運行到上例的g_loop->cancel時,toCancel代表的Timer已經不在timers_和activeTimers_這兩個容器中,而是位於getExpired方法中的expired中。之所以要保存取消的定時器,上文方法cancelInLoop也已說明,是爲了給重置定時器的方法使用。在重置定時器期間(即reset方法),如果有取消定時器行爲,TimerQueue會記住在本次調用到期Timer期間有哪些cancel()請求,並且不再把已cancel()的Timer添加到timers_和activeTimers_當中。

三、體會

  • sleep()、alarm()、usleep()在實現時有可能用了SIGALRM信號,在多線程程序中處理信號是個相當麻煩的事情,應當儘量避免。
  • linux timerfd_create()把時間變成了一個文件描述符,該“文件”在定時器超時那一刻變得可讀,這樣就能很方便的融入select()、poll()框架中,用統一的方式來處理IO事件和超時事件。
  • 對於不支持timerfd_create()的環境,可以考慮起一個線程來模擬timerfd功能,到期往特定描述符寫入一定數據。這樣可以達到統一管理文件描述符的方式來編碼。
  • rvo優化。
  • 另外一點就是多體會Muduo編碼思想。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章