1. 程式人生 > >淺析muduo庫中的定時器設施

淺析muduo庫中的定時器設施

val read eid 就是 RR 重新 cal using dss

一個設計良好的定時器在服務端的應用程序上至關重要,muduo定時器的實現陳碩大牛在書中已經詳細的談過,筆者嘗試從源碼的角度解讀定時器的實現,如果理解不對,歡迎指正。
在muduo的定時器系統中,一共由四個類:Timestamp,Timer,TimeId,TimerQueue組成。其中最關鍵的是Timer和TimerQueue兩個類。此文只解釋初讀時讓人非常迷惑的TimerQueue類,這個類是整個定時器設施的核心,其他三個類簡介其作用。
其中Timestamp是一個以int64_t表示的微秒級絕對時間,而Timer則表示一個定時器的到時事件,是否具有重復喚醒的時間等,TimerId表示在在TimerQueue中對Timer的索引。

TimerQueue

下面是muduo定時器中最重要的TimerQueue類,是整個定時器的核心,初讀時讓人非常迷惑,最主要的原因還是沒有搞清楚Timer類中的成員的意思。

/**Timer.h**/
 private:
  const TimerCallback callback_;//定時器回調函數
  Timestamp expiration_;//絕對的時間
  const double interval_;//如果有重復屬性,超時的時間間隔
  const bool repeat_;//是否有重復
  const int64_t sequence_;//定時器序號

  static AtomicInt64 s_numCreated_;//定時器計數

有了上述成員的意義,我們便可以介紹TimerQueue的功能了。

/**TimerQueue.h**/
class TimerQueue : boost::noncopyable
{
 public:
  TimerQueue(EventLoop* loop);
  ~TimerQueue();

  ///
  /// Schedules the callback to be run at given time,
  /// repeats if @c interval > 0.0.
  ///
  /// Must be thread safe. Usually be called from other threads.
  TimerId addTimer(const TimerCallback& cb,
                   Timestamp when,
                   double interval);//往定時器隊列中添加定時器
#ifdef __GXX_EXPERIMENTAL_CXX0X__
  TimerId addTimer(TimerCallback&& cb,
                   Timestamp when,
                   double interval);
#endif

  void cancel(TimerId timerId);//取消某個定時器

 private:

  // FIXME: use unique_ptr<Timer> instead of raw pointers.
  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);
  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_;//定時器關心的channel對象
  // Timer list sorted by expiration
  TimerList timers_;//定時器集合(有序)

  // for cancel()
  // activeTimerSet和timer_保存的是相同的數據
  // timers_是按照到期的時間排序的,activeTimerSet_是按照對象地址排序
  ActiveTimerSet activeTimers_;//保存正在活動的定時器(無序)
  bool callingExpiredTimers_; /* atomic *///是否正在處理超時事件
  ActiveTimerSet cancelingTimers_;//保存的是取消的定時器(無序)
};

上述代碼中有三處讓人感到驚喜的地方:

  • 首先,整個TimerQueue之打開一個timefd,用以觀察定時器隊列隊首的到期事件。其原因是因為set容器是一個有序隊列,以<排序,就是說整個隊列中,Timer的到期時間時從小到大排列的,正是因為這樣,才能做到節省系統資源的目的。
  • 其次,在整個TimerQueue類中有三個容器,一個表示有序的Timer隊列,一個表示正在活動的,無序的定時器隊列(用於與有序的定時器隊列同步),還有一個表示取消的定時器隊列(在重新啟動一個有固定時間間隔定時器時,首先判斷是否友重復屬性,其次就是是否在已經取消的隊列中)。第二個定時器隊列是否多余?還沒有想明白。
  • 最後,整個定時器隊列采用了muduo典型的事件分發機制,可以使的定時器的到期時間像fd一樣在Loop線程中處理。
    ```
    /TimerQueue.cc/
    int createTimerfd()
    {//創建非阻塞timefd
    int timerfd = ::timerfd_create(CLOCK_MONOTONIC,
    TFD_NONBLOCK | TFD_CLOEXEC);
    if (timerfd < 0)
    {
    LOG_SYSFATAL << "Failed in timerfd_create";
    }
    return timerfd;
    }

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

void readTimerfd(int timerfd, Timestamp now)
{//處理超時時間,超時後,timefd變為可讀,howmany表示超時的次數
uint64_t howmany;//將事件讀出來,免得陷入Loop忙碌狀態
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";
}
}

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()";
}
}

}
}
}

using namespace muduo;
using namespace muduo::net;
using namespace muduo::net::detail;

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();//設置Channel的常規步驟
}

TimerQueue::~TimerQueue()
{
timerfdChannel_.disableAll();//channel不再關註任何事件
timerfdChannel_.remove();//在三角循環中刪除此Channel
::close(timerfd_);
// do not remove channel, since we‘re in EventLoop::dtor();
for (TimerList::iterator it = timers_.begin();
it != timers_.end(); ++it)
{
delete it->second;//釋放timer對象
}
}

TimerId TimerQueue::addTimer(const TimerCallback& cb,
Timestamp when,
double interval)
{//添加新的定時器
Timer* timer = new Timer(cb, when, interval);
loop_->runInLoop(
boost::bind(&TimerQueue::addTimerInLoop, this, timer));
return TimerId(timer, timer->sequence());
}

ifdef GXX_EXPERIMENTAL_CXX0X

TimerId TimerQueue::addTimer(TimerCallback&& cb,
Timestamp when,
double interval)
{
Timer* timer = new Timer(std::move(cb), when, interval);
loop_->runInLoop(
boost::bind(&TimerQueue::addTimerInLoop, this, timer));
return TimerId(timer, timer->sequence());
}

endif

void TimerQueue::cancel(TimerId timerId)
{//取消定時器
loop_->runInLoop(
boost::bind(&TimerQueue::cancelInLoop, this, timerId));
}

void TimerQueue::addTimerInLoop(Timer* timer)
{
loop_->assertInLoopThread();
bool earliestChanged = insert(timer);//是否將timer插入set的首部

//如果插入首部,更新timrfd關註的到期時間
if (earliestChanged)
{
resetTimerfd(timerfd_, timer->expiration());//啟動定時器
}
}

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())
{//刪除Timers_和activeTimers_中的Timer
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);//刪除活動的timer
}
else if (callingExpiredTimers_)
{//將刪除的timer加入到取消的timer隊列中
cancelingTimers_.insert(timer);//取消的定時器與重新啟動定時器有沖突
}
assert(timers_.size() == activeTimers_.size());
}

void TimerQueue::handleRead()
{
loop_->assertInLoopThread();
Timestamp now(Timestamp::now());
readTimerfd(timerfd_, now);//讀timerFd,防止一直出現可讀事件,造成loop忙碌

std::vector

callingExpiredTimers_ = true;//將目前的狀態調整為處理超時狀態
cancelingTimers_.clear();//將取消的定時器清理掉
//更新完成馬上就是重置,重置時依賴已經取消的定時器的條件,所以要將取消的定時器的隊列清空
// safe to callback outside critical section
for (std::vector

reset(expired, now);//把具有重復屬性的定時器重新加入定時器隊列中
}

std::vector

for (std::vector

assert(timers_.size() == activeTimers_.size());//再次將timer_和activetimer同步
return expired;//返回超時的timerQueue
}

void TimerQueue::reset(const std::vector

for (std::vector

if (!timers_.empty())
{//如果目前的隊列不為空,獲得目前隊首的到期時間
nextExpire = timers_.begin()->second->expiration();
}

if (nextExpire.valid())
{//如果到期時間不為0,重新設置timerfd應該關註的時間
resetTimerfd(timerfd_, nextExpire);
}
}

bool TimerQueue::insert(Timer* timer)
{//將Timer插入到兩個同步的TimeQueue中,最關鍵的一個函數
loop_->assertInLoopThread();
assert(timers_.size() == activeTimers_.size());//判斷兩個Timer隊列的同步bool earliestChanged = false;
Timestamp when = timer->expiration();//獲得Timer的事件
TimerList::iterator it = timers_.begin();//得到Timer的begin
if (it == timers_.end() || when < it->first)
{//判斷是否要將這個timer插入隊首,如果是,更新timefd關註的到期事件
earliestChanged = true;
}

{//將Timer中按順序插入timer_,set是有序集合,默認關鍵字<排列
std::pair

{//隨意插入進入activeTimer_
std::pair

assert(timers_.size() == activeTimers_.size());//再次同步兩個Timer
return earliestChanged;
}
```
上述代碼註釋足夠多,還是那個問題,無序的set是否有出現的必要?

淺析muduo庫中的定時器設施