muduo源码学习(17)-定时器实现
来源:互联网 发布:好看的棒球服淘宝店 编辑:程序博客网 时间:2024/05/29 09:39
一个网络库中不仅要处理网络IO事件,还要处理定时事件,有就是什么时候要执行什么函数。首先封装了一个定时器类Timer,在net/Timer.h中
class Timer : boost::noncopyable{ public: Timer(const TimerCallback& cb, Timestamp when, double interval) : callback_(cb), expiration_(when), interval_(interval), repeat_(interval > 0.0), sequence_(s_numCreated_.incrementAndGet()) { }//执行定时函数 void run() const { callback_(); } Timestamp expiration() const { return expiration_; } bool repeat() const { return repeat_; } int64_t sequence() const { return sequence_; } void restart(Timestamp now); static int64_t numCreated() { return s_numCreated_.get(); } private://定时所需要执行的函数 const TimerCallback callback_; //执行函数的时间点 Timestamp expiration_; //频率 const double interval_; //是否重复 const bool repeat_; //序号 const int64_t sequence_; //记录创建的对象的个数 static AtomicInt64 s_numCreated_;};有些定时器是需要重复执行的,也就是执行一次后,要开始等待执行,因此该类中有repeat_字段。该类比较简单。
为了在定时器超时的时候得到通知,muduo使用的是timerfd_create函数
int timer_create(clockid_t clock_id, struct sigevent *evp, timer_t *timerid)timer_create()所创建的定时器并未启动。要将它关联到一个到期时间以及启动时钟周期,可以使用timer_settime()。
int timer_settime(timer_t timerid, int flags, const struct itimerspec *value, struct itimerspect *ovalue);struct itimespec{ struct timespec it_interval; struct timespec it_value;};如同settimer(),it_value用于指定当前的定时器到期时间。当定时器到期,it_value的值会被更新成it_interval 的值。如果it_interval的值为0,则定时器不是一个时间间隔定时器,一旦it_value到期就会回到未启动状态。timespec的结构提供了纳秒级分辨率:
struct timespec{ time_t tv_sec; long tv_nsec; };简单来说timerfd_create函数是创建一个描述符,timer_settime是设置一个时间点,到该时刻的时候描述符可读,因此可以很方便的将定时事件转换为描述符可读事件,以此来使用epoll监听。
为了维护许多定时事件,需要有定时器的管理容器。该类是TimerQueue。net/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);//取消 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 //timerfd可读的时候在loop循环中被调用 void handleRead(); // move out all expired timers std::vector<Entry> getExpired(Timestamp now); svoid reset(const std::vector<Entry>& expired, Timestamp now); bool insert(Timer* timer); EventLoop* loop_; const int timerfd_; Channel timerfdChannel_; // Timer list sorted by expiration //set容器 TimerList timers_; // for cancel() // ActiveTimerSet activeTimers_; bool callingExpiredTimers_; /* atomic */ ActiveTimerSet cancelingTimers_;};有两个set容器存放定时器,timerfd_就是定时事件的描述符。主要函数就是addTimer添加定时器。构造函数中:
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. //加入到epoll中管理 timerfdChannel_.enableReading();}
为了在一个线程中往另一个线程中添加定时器,addtimer函数调用了adderTimerInLoop,也就是加入到额EventLoop中的函数指针的容器中。最终调用了insert函数
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;}在添加的时候,如果添加的定时器的超时时间点比原来的定时器中的所有的超时时间点都早,则需要更新超时秒数符的超时时间点。
在定时器描述符可读是,调用了handleEvent函数,而handleEvent又调用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 (std::vector<Entry>::iterator it = expired.begin(); it != expired.end(); ++it) { //调用定时器的回调函数 it->second->run(); } callingExpiredTimers_ = false;// reset(expired, now);}getExpired实现如下:
std::vector<TimerQueue::Entry> TimerQueue::getExpired(Timestamp now){ assert(timers_.size() == activeTimers_.size()); //typedef std::pair<Timestamp, Timer*> Entry; std::vector<Entry> expired; Entry sentry(now, reinterpret_cast<Timer*>(UINTPTR_MAX));//lower_bound() 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 (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;}在timers_是set容器,根据超时时间排序的,timers_中找到超时的定时器,移除。之后调用reset函数,因为有的定时器是需要重复执行的。
取消定时器函数cancleInLoop:
void TimerQueue::cancelInLoop(TimerId timerId){ loop_->assertInLoopThread(); assert(timers_.size() == activeTimers_.size()); // typedef std::pair<Timer*, int64_t> ActiveTimer; 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());}
TimerQueue中有两个定时器容器,存储的内容是一样的,排序情况不同,是为了方便的查找。
在EventLoop中,有一个TimerQueue指针,还有如下:
TimerId EventLoop::runAt(const Timestamp& time, const TimerCallback& cb){ return timerQueue_->addTimer(cb, time, 0.0);}TimerId EventLoop::runAfter(double delay, const TimerCallback& cb){ Timestamp time(addTime(Timestamp::now(), delay)); return runAt(time, cb);}TimerId EventLoop::runEvery(double interval, const TimerCallback& cb){ Timestamp time(addTime(Timestamp::now(), interval)); return timerQueue_->addTimer(cb, time, interval);}对定时器容器的操作最终是通过EventLoop间接调用的。
阅读全文
0 0
- muduo源码学习(17)-定时器实现
- muduo源码分析之定时器TimerQueue的设计与实现
- muduo网络库学习笔记(10):定时器的实现
- muduo源码学习(1)
- muduo网络库源码分析-定时器
- muduo网络库定时器的实现
- muduo网络库定时器的实现
- muduo源码学习(7)-队列
- muduo源码学习(8)-ThreadPool
- muduo源码学习(9)-单例类
- muduo源码学习(10)-ThreadLocal
- muduo源码学习(18)-EventLoopThread
- muduo源码分析--EventLoop 类的实现
- muduo源码分析--线程池的实现
- muduo源码学习(2)-Timestamp
- muduo源码学习(3)-原子操作
- muduo源码学习(4)-自定义异常
- muduo源码学习(5)-线程封装
- 日语入门学习,五十音图日语基础知识
- 双端队列
- 三种简单排序
- 剑指offer面试题4
- java应用中Pattern和Matcher的使用
- muduo源码学习(17)-定时器实现
- Linux
- iOS百度地图 定位,获取当前经纬度
- macvlan 网络隔离和连通
- 小程序五彩圈代码,记录
- php foreach行声明变量的作用范围问题
- Android StatusBar 黑底白字
- POJ
- Jsoup组件抓取HTML标签