muduo网络库学习(三)定时器TimerQueue的设计
Linux下用于獲取當(dāng)前時(shí)間的函數(shù)有
- time(2) / time_t (秒)
- ftime(3) / struct timeb (毫秒)
- gettimeofday(2) / struct timeval (微秒)
- clock_gettime(2) / struct timespec (納秒)
定時(shí)函數(shù),用于讓程序等待一段時(shí)間或安排計(jì)劃任務(wù)
- sleep(3)
- alarm(2)
- usleep(3)
- nanosleep(2)
- clock_nanosleep(2)
- getitimer(2) / setitimer(2)
- timer_create(2) / timer_settime(2) / timer_gettime(2) / timer_delete(2)
- timerfd_create(2) / timerfd_gettime(2) / timerfd_settime(2)
muduo的取舍是
- (計(jì)時(shí))只使用gettimeofday(2)來(lái)獲取當(dāng)前時(shí)間
- (定時(shí))只使用timerfd_*系列函數(shù)來(lái)處理定時(shí)任務(wù)
gettimeofday入選的原因
timerfd*_入選的原因
select/poll/epoll可以設(shè)置timeout來(lái)實(shí)現(xiàn)超時(shí),但是poll/epoll的精度只有毫秒,遠(yuǎn)低于timerfd_settime
---------------------------------------摘自《Linux多線程服務(wù)器編程》gettimeofday
#include <sys/time.h> int gettimeofday(struct timeval* tv, struct timezone* tz); /* * 返回后將目前的時(shí)間存放在tv中,把時(shí)區(qū)信息存放在tz中 * tz和tz都可以為NULL* 執(zhí)行成功返回0,失敗返回-1*/struct timeval{long tv_sec; /* 秒 */long tv_usec; /* 微秒 */ };struct timeval timer; gettimeofday(&timer, NULL);timerfd_*系列函數(shù)
#include <sys/timerfd.h> int timerfd_create(int clockid, int flags); /** 創(chuàng)建一個(gè)用于定時(shí)器的文件描述符* clockid可以為* CLOCK_REALTIME(系統(tǒng)實(shí)時(shí)時(shí)間,可能會(huì)被用戶手動(dòng)更改)* CLOCK_MONOTONIC(從系統(tǒng)啟動(dòng)那一刻開(kāi)始計(jì)時(shí)的時(shí)間,無(wú)法被用戶更改)* flags可以為* TFD_NONBLOCK(非阻塞)* TFD_CLOEXEC(調(diào)用exec時(shí)自動(dòng)close)*/int timerfd_settime(int fd, int flag, const struct itimerspec* new_value,const struct itimerspec* old_value); /** 用于設(shè)置timerfd的超時(shí)時(shí)間* fd, 通過(guò)timerfd_create返回的文件描述符* flag, 0代表相對(duì)時(shí)間,TFD_TIMER_ABSTIME代表絕對(duì)時(shí)間* new_value, 新設(shè)置的超時(shí)時(shí)間* old_value, 以前設(shè)置的超時(shí)時(shí)間,值-結(jié)果參數(shù)* * struct timespec{* time_t tv_sec;* time_t tv_nsec;* };* * struct itimerspec{* struct timespec it_interval;* struct timespce it_value;* }; * * it_value表示首次超時(shí)時(shí)間* it_interval表示后續(xù)周期性超時(shí)時(shí)間* it_interval不為0表示是周期性超時(shí)任務(wù)* it_interval和it_value同時(shí)為0表示取消定時(shí)任務(wù)*/int timerfd_gettime(int fd, struct itimerspec *cur_value); /** 返回距離下次超時(shí)還剩多長(zhǎng)時(shí)間,保存在cur_value中* 如果調(diào)用時(shí)定時(shí)器已經(jīng)到期,同時(shí)定時(shí)器設(shè)置了周期性任務(wù)(it_interval不為0)* 那么調(diào)用此函數(shù)之后定時(shí)器重新開(kāi)始計(jì)時(shí),超時(shí)時(shí)間是it_interval的值*/Timestamp類(lèi)用于保存超時(shí)時(shí)間,類(lèi)中只有一個(gè)成員變量,保存當(dāng)前UTC時(shí)間,即從Unix Epoch(1970-01-01 00:00:00)到現(xiàn)在的微秒數(shù)
Timer類(lèi)是一個(gè)超時(shí)任務(wù),保存超時(shí)時(shí)間Timestamp,回調(diào)函數(shù),以及記錄自己是否是周期性計(jì)時(shí)任務(wù),回調(diào)函數(shù)是用戶提供的
TimerId類(lèi)用于保存超時(shí)任務(wù)Timer和它獨(dú)一無(wú)二的id
TimerQueue類(lèi)保存用戶設(shè)置的所有超時(shí)任務(wù),需要高效保存尚未超時(shí)的任務(wù),同時(shí)需要有序,方便找到超時(shí)時(shí)間最近的那個(gè)任務(wù),可以用最小堆(libevent采用),也可以用std::set存儲(chǔ)(muduo采用)
二者不同之處在于
- libevent將超時(shí)任務(wù)也封裝在struct event中(類(lèi)似muduo的Channel),最小堆中存放的也就直接是struct event,這么做的原因是libevent支持對(duì)某個(gè)文件描述符的超時(shí)監(jiān)聽(tīng),包括tcp連接的fd。
- muduo是另封裝了一個(gè)Timer用與表示定時(shí)任務(wù),并采用std::pair<Timerstamp, Timer*>作為std::set的鍵,通過(guò)比較超時(shí)時(shí)間來(lái)排序(std::set內(nèi)部采用紅黑樹(shù),一種二叉搜索樹(shù))
對(duì)于定時(shí)任務(wù)的原理,
整個(gè)過(guò)程只有一個(gè)timerfd被Poller監(jiān)聽(tīng),所以調(diào)用timerfd_settime設(shè)置的超時(shí)時(shí)間一定是TimerQueue的set里最小的,即set.begin();第一個(gè)Timer任務(wù)。
而用戶是通過(guò)調(diào)用EventLoop::runAt/runAfter/runEvery函數(shù)注冊(cè)定時(shí)任務(wù)的,這些函數(shù)都需要向TimerQueue的set中添加Timer,所以每添加一個(gè)都需要判斷新添加的定時(shí)任務(wù)的超時(shí)時(shí)間是否小于設(shè)置的超時(shí)時(shí)間,如果小于,就需要調(diào)用timerfd_settime重新設(shè)置timerfd的超時(shí)時(shí)間。
而每次timerfd被激活都需要找到在set中所有的超時(shí)任務(wù),因?yàn)橛锌赡艽嬖诔瑫r(shí)時(shí)間相等的定時(shí)任務(wù),可以使用std::lower_bound函數(shù)找到第一個(gè)大于等于給定值的位置
TimerQueue由所在的EventLoop持有,用戶設(shè)置定時(shí)任務(wù)也是調(diào)用的EventLoop的接口,進(jìn)而調(diào)用TimerQueue的接口
EventLoop提供三個(gè)接口用于注冊(cè)定時(shí)任務(wù),進(jìn)而調(diào)用TimerQueue的addTimer接口
TimerQueue的定義如下,主要就是保存著timerfd和所有的定時(shí)任務(wù),回調(diào)函數(shù),以及添加/刪除定時(shí)任務(wù)的函數(shù)
/* 前向聲明,避免#include */ class EventLoop; class Timer; class TimerId;/// /// A best efforts timer queue. /// No guarantee that the callback will be on time. /// class TimerQueue : noncopyable {public:explicit 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./* * 用于注冊(cè)定時(shí)任務(wù)* @param cb, 超時(shí)調(diào)用的回調(diào)函數(shù)* @param when,超時(shí)時(shí)間(絕對(duì)時(shí)間)* @interval,是否是周期性超時(shí)任務(wù)*/TimerId addTimer(TimerCallback cb,Timestamp when,double interval);/* 取消定時(shí)任務(wù),每個(gè)定時(shí)任務(wù)都有對(duì)應(yīng)的TimerId,這是addTimer返回給調(diào)用者的 */void cancel(TimerId timerId);private:// 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;/* * 主要用于刪除操作,通過(guò)TimerId找到Timer*,再通過(guò)Timer*找到在timers_中的位置,將期刪除* 覺(jué)得可以省略*/typedef std::pair<Timer*, int64_t> ActiveTimer;typedef std::set<ActiveTimer> ActiveTimerSet;void addTimerInLoop(Timer* timer);void cancelInLoop(TimerId timerId);// called when timerfd alarms/* 當(dāng)timerfd被激活時(shí)調(diào)用的回調(diào)函數(shù),表示超時(shí) */void handleRead();// move out all expired timers/* 從timers_中拿出所有超時(shí)的Timer* */std::vector<Entry> getExpired(Timestamp now);/* 將超時(shí)任務(wù)中周期性的任務(wù)重新添加到timers_中 */void reset(const std::vector<Entry>& expired, Timestamp now);/* 插入到timers_中 */bool insert(Timer* timer);/* 所屬的事件驅(qū)動(dòng)循環(huán) */EventLoop* loop_;/* 由timerfd_create創(chuàng)建的文件描述符 */const int timerfd_;/* 用于監(jiān)聽(tīng)timerfd的Channel */Channel timerfdChannel_;// Timer list sorted by expiration/* 保存所有的定時(shí)任務(wù) */TimerList timers_;// for cancel()ActiveTimerSet activeTimers_;bool callingExpiredTimers_; /* atomic */ActiveTimerSet cancelingTimers_; };.cpp中主要是添加和回調(diào)函數(shù),添加函數(shù)是addTimer,由用戶調(diào)用EventLoop::run*,再由runAt調(diào)用addTimer
/** 用戶調(diào)用runAt/runAfter/runEveny后由EventLoop調(diào)用的函數(shù)* 向時(shí)間set中添加時(shí)間* * @param cb,用戶提供的回調(diào)函數(shù),當(dāng)時(shí)間到了會(huì)執(zhí)行* @param when,超時(shí)時(shí)間,絕對(duì)時(shí)間* @param interval,是否調(diào)用runEveny,即是否是永久的,激活一次后是否繼續(xù)等待* * std::move,避免拷貝,移動(dòng)語(yǔ)義* std::bind,綁定函數(shù)和對(duì)象,生成函數(shù)指針*/ TimerId TimerQueue::addTimer(TimerCallback cb,Timestamp when,double interval) {Timer* timer = new Timer(std::move(cb), when, interval);/* * 在自己所屬線程調(diào)用addTimerInLoop函數(shù) * 用戶只能通過(guò)初始創(chuàng)建的EventLoop調(diào)用addTimer,為什么還會(huì)考慮線程問(wèn)題 why?* 這個(gè)線程和TcpServer的線程應(yīng)該是同一個(gè)*/loop_->runInLoop(std::bind(&TimerQueue::addTimerInLoop, this, timer));return TimerId(timer, timer->sequence()); }函數(shù)中主要將執(zhí)行任務(wù)交給addTimerInLoop,感覺(jué)這里不太需要這樣做
/* 向計(jì)時(shí)器隊(duì)列中添加超時(shí)事件 */ void TimerQueue::addTimerInLoop(Timer* timer) {loop_->assertInLoopThread();/* 返回true,說(shuō)明timer被添加到set的頂部,作為新的根節(jié)點(diǎn),需要更新timerfd的激活時(shí)間 */bool earliestChanged = insert(timer);if (earliestChanged){resetTimerfd(timerfd_, timer->expiration());} }插入函數(shù)主要任務(wù)是將某個(gè)定時(shí)任務(wù)插入到定時(shí)任務(wù)set中,同時(shí)判斷新添加的這個(gè)定時(shí)任務(wù)的超時(shí)時(shí)間和之前設(shè)置的超時(shí)時(shí)間的大小(位于set的根節(jié)點(diǎn)處),如果新添加的定時(shí)任務(wù)超時(shí)時(shí)間小,就需要更新timerfd的超時(shí)時(shí)間(返回true),然后調(diào)用resetTimerfd使用timerfd_settime重新設(shè)置超時(shí)時(shí)間
bool TimerQueue::insert(Timer* timer) {loop_->assertInLoopThread();assert(timers_.size() == activeTimers_.size());bool earliestChanged = false;/* 獲取timer的UTC時(shí)間戳,和timer組成std::pair<Timestamp, Timer*> */Timestamp when = timer->expiration();/* timers_begin()是set頂層元素(紅黑樹(shù)根節(jié)點(diǎn)),是超時(shí)時(shí)間最近的Timer* */TimerList::iterator it = timers_.begin();/* 如果要添加的timer的超時(shí)時(shí)間比timers_中的超時(shí)時(shí)間近,更改新的超時(shí)時(shí)間 */if (it == timers_.end() || when < it->first){earliestChanged = true;}{/* 添加到定時(shí)任務(wù)的set中 */std::pair<TimerList::iterator, bool> result= timers_.insert(Entry(when, timer));assert(result.second); (void)result;}{/* 同時(shí)也添加到activeTimers_中,用于刪除時(shí)查找操作 */std::pair<ActiveTimerSet::iterator, bool> result= activeTimers_.insert(ActiveTimer(timer, timer->sequence()));assert(result.second); (void)result;}assert(timers_.size() == activeTimers_.size());return earliestChanged; }當(dāng)timerfd被激活,表明定時(shí)任務(wù)超時(shí),進(jìn)而調(diào)用回調(diào)函數(shù),即TimerQueue::handleRead,這個(gè)回調(diào)函數(shù)是在構(gòu)造函數(shù)中構(gòu)造Channel時(shí)候注冊(cè)的
回調(diào)函數(shù)先調(diào)用getExpired從定時(shí)任務(wù)set中取出所有超時(shí)任務(wù),然后執(zhí)行其回調(diào)函數(shù),最后判斷取出的這些超時(shí)任務(wù)有沒(méi)有周期性的,如果有,就將周期性任務(wù)添加回set中
/* * 當(dāng)定時(shí)器超時(shí),保存timerfd的Channel激活,調(diào)用回調(diào)函數(shù)*/ void TimerQueue::handleRead() {loop_->assertInLoopThread();Timestamp now(Timestamp::now());readTimerfd(timerfd_, now);/* 從定時(shí)任務(wù)set中拿出所有超時(shí)任務(wù) */std::vector<Entry> expired = getExpired(now);callingExpiredTimers_ = true;cancelingTimers_.clear();// safe to callback outside critical section/* 調(diào)用超時(shí)的事件回調(diào)函數(shù) */for (std::vector<Entry>::iterator it = expired.begin();it != expired.end(); ++it){it->second->run();}callingExpiredTimers_ = false;reset(expired, now); }getExpired主要就是將set中的超時(shí)任務(wù)拿出,因?yàn)閟et是有序的,直接調(diào)用std::lower_bound找到第一個(gè)大于等于當(dāng)前時(shí)間的定時(shí)任務(wù),前面的所有任務(wù)都是超時(shí)的,全部取出
感覺(jué)應(yīng)該使用std::upper_bound找到第一個(gè)大于當(dāng)前時(shí)間的任務(wù),如果超時(shí)時(shí)間和當(dāng)前時(shí)間相等,應(yīng)該算作超時(shí)才對(duì)?
調(diào)用完回調(diào)函數(shù)之后需要將周期性任務(wù)重新添加到set中,不過(guò)記得要重新計(jì)算超時(shí)時(shí)間
/* * 調(diào)用完所有超時(shí)的回調(diào)函數(shù)后,需要對(duì)這些超時(shí)任務(wù)進(jìn)行整理* 將周期性的定時(shí)任務(wù)重新添加到set中*/ 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() /* 是否是周期性的定時(shí)任務(wù) */&& cancelingTimers_.find(timer) == cancelingTimers_.end()) /* 如果用戶手動(dòng)刪除了這個(gè)定時(shí)任務(wù),就不添加了 */{/* 重新計(jì)算超時(shí)時(shí)間 */it->second->restart(now);/* 重新添加到set中 */insert(it->second);}else{// FIXME move to a free listdelete it->second; // FIXME: no delete please}}/* 計(jì)算下次timerfd被激活的時(shí)間 */if (!timers_.empty()){nextExpire = timers_.begin()->second->expiration();}/* 設(shè)置 */if (nextExpire.valid()){resetTimerfd(timerfd_, nextExpire);} }muduo將超時(shí)事件轉(zhuǎn)換成文件描述符的可讀事件,統(tǒng)一到io復(fù)用函數(shù)中,達(dá)到統(tǒng)一的效果。另外并沒(méi)有將所有的定時(shí)任務(wù)都創(chuàng)建一個(gè)timerfd而是取最早超時(shí)的那個(gè)時(shí)間作為timerfd的超時(shí)時(shí)間,一方面減少內(nèi)存,節(jié)省描述符,另一方面更方便管理。不過(guò)需要記得對(duì)超時(shí)時(shí)間的更新,因?yàn)橛脩粼俅翁砑拥亩〞r(shí)任務(wù)的超時(shí)時(shí)間可能早于先前設(shè)置的時(shí)間
幾個(gè)C++方面的知識(shí)點(diǎn)
- std::lower_bound,找到第一個(gè)大于等于給定值的位置,返回迭代器
- std::back_inserter,獲取末尾位置的迭代器
- std::move,移動(dòng)語(yǔ)義,避免拷貝,可以和右值引用一起使用
- std::bind,綁定函數(shù)指針和對(duì)象
- std::function,創(chuàng)建函數(shù)對(duì)象類(lèi)型
總結(jié)
以上是生活随笔為你收集整理的muduo网络库学习(三)定时器TimerQueue的设计的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: muduo网络库学习(二)对套接字和监听
- 下一篇: muduo网络库学习(四)事件驱动循环E