muduo源码阅读笔记(8、定时器TimerQueue)
muduo源码阅读笔记(8、定时器TimerQueue)
Muduo源码笔记系列:
muduo源码阅读笔记(2、对C语言原生的线程安全以及同步的API的封装)
muduo源码阅读笔记(6、EvevntLoop和Thread)
muduo源码阅读笔记(7、EventLoopThreadPool)
前言
为了方便Poller的管理,Muduo使用了基于文件描述符的定时器。
实现
定时器提供的接口:
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. TimerId addTimer(TimerCallback cb, Timestamp when, double interval); 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; 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_; // 定时器和哪个EventLoop关联 const int timerfd_; // timerfd_ Channel timerfdChannel_; // 基于timerfd_的Channel // Timer list sorted by expiration TimerList timers_; // 基于set的定时器(Timestamp,Timer*) // for cancel() ActiveTimerSet activeTimers_; bool callingExpiredTimers_; /* atomic */ ActiveTimerSet cancelingTimers_; // (Timer*,int64_t) };
构造函数:
在每个EventLoop创建时,在自己的构造函数中,创建自己的定时器TimerQueue
,并将EventLoop的this指针作为TimerQueue构造函数的参数。TimerQueue的构造会创建一个timerfd,并且向EventLoop的Poller注册timerfd。这样,Poller正式开开始管理定时器。后面的Acceptor、TcpConnection使用了类似的手法。
实现如下:
/* * @param: EventLoop的this指针 */ TimerQueue::TimerQueue(EventLoop* loop) : loop_(loop), timerfd_(createTimerfd()), timerfdChannel_(loop, timerfd_), timers_(), callingExpiredTimers_(false) { timerfdChannel_.setReadCallback( std::bind(&TimerQueue::handleRead, this)); // we are always reading the timerfd, we disarm it with timerfd_settime. timerfdChannel_.enableReading(); // 向所在的loop中注册timerfd。 }
关于<号的万能性
将自定义类存入std::set是要求用户实现自定义对象<号重载的。思考一个问题:只重载<的话,如果用户调用find成员函数时,set如何判断两个对象是否相等呢?
其实std::set内部做两次比较即可判断两个对象是否相等。方法:当a < b == false && b < a == false时,说明此时 a == b。读者可以在这里仔细思考一下。Timestamp正是因为实现了<才可以作为std::set的元素类型。
一个自定义对象重载<号后,不光可以通过<推导出==,还可以推到出>、>=、<=号。参考博客
参考boost::less_than_comparable的实现,如下:
//已知: friend bool operator<(const T& x, const T& y) { /*...*/} // | // V //可以推导: friend bool operator>(const T& x, const T& y) { return y < x; } friend bool operator<=(const T& x, const T& y) { return !static_cast<bool>(y < x); } friend bool operator>=(const T& x, const T& y) { return !static_cast<bool>(x < y); }
定时器实现的伪代码:
TimerId TimerQueue::addTimer(TimerCallback cb, Timestamp when, double interval){ Timer* timer = new Timer(std::move(cb), when, interval); loop_->runInLoop( std::bind(&TimerQueue::addTimerInLoop, this, timer)); return TimerId(timer, timer->sequence()); } void TimerQueue::cancel(TimerId timerId){ loop_->runInLoop( std::bind(&TimerQueue::cancelInLoop, this, timerId)); } void TimerQueue::addTimerInLoop(Timer* timer){ loop_->assertInLoopThread(); bool earliestChanged = insert(timer); // timer加入最新超时的定时器被更新。 if (earliestChanged){ // 更新timerfd_的超时时间 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()){ // 在activeTimers_上 // 在timers_上删除timerId delete it->first; // FIXME: no delete please // 在activeTimers_上删除timerId }else if (callingExpiredTimers_){ // 如果正在处理超时定时器,那么timerId是有可能从activeTimers_上移除,而在handleRead::expired中 // 所以先将timerId加入cancelingTimers_列表,防止是循环定时器,又被重新加入到activeTimers_。handleRead会调用reset删除被取消的定时器。 cancelingTimers_.insert(timer); } assert(timers_.size() == activeTimers_.size()); } void TimerQueue::handleRead(){ // timerfd_读事件处理回调 loop_->assertInLoopThread(); Timestamp now(Timestamp::now()); readTimerfd(timerfd_, now); // 清空timerfd_上的数据 std::vector<Entry> expired = getExpired(now); callingExpiredTimers_ = true; cancelingTimers_.clear(); // safe to callback outside critical section for (const Entry& it : expired){ it.second->run(); // 调用过期定时器的回调 } callingExpiredTimers_ = false; reset(expired, now); // 看能不能重新安装过期定时器,不能就delete。 } std::vector<TimerQueue::Entry> TimerQueue::getExpired(Timestamp now){ assert(timers_.size() == activeTimers_.size()); std::vector<Entry> expired; // 根据now,在timers_中找过期的定时器,存入expired。 // ... for (const Entry& it : expired){ // 同步activeTimers_ 和 timers_ // ... } assert(timers_.size() == activeTimers_.size()); return expired; } void TimerQueue::reset(const std::vector<Entry>& expired, Timestamp now){ Timestamp nextExpire; for (const Entry& it : expired){ ActiveTimer timer(it.second, it.second->sequence()); if (it.second->repeat() && cancelingTimers_.find(timer) == cancelingTimers_.end()){ // 是循环定时器,并且没有被取消。 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); } } bool TimerQueue::insert(Timer* timer){ loop_->assertInLoopThread(); assert(timers_.size() == activeTimers_.size()); bool earliestChanged = false; Timestamp when = timer->expiration(); // timer超时时间 TimerList::iterator it = timers_.begin(); // 原来定时器中最早超时的定时器 if (it == timers_.end() || when < it->first){ // 原本timers_就没有定时器 || 要插入的定时器超时时间 比 原来的timers_中第一个定时器 早。 // 都代表:插入新定时器后,最早超时时间会发生改变,需要重新设置timeFd。 earliestChanged = true; } // 插入timers_ // std::set::insert // 同步到activeTimers_ // std::set::insert return earliestChanged; }
细节明细:
疑问
定时器模块存在的意义?
解答
-
事件触发机制: 定时器在Muduo中被用作一种事件触发机制。通过设置定时器,用户可以在指定的时间间隔内执行相应的操作,例如执行定时任务、发送心跳包等。这种事件触发机制有助于异步编程中的任务调度和协调。
-
超时处理: 定时器用于处理超时事件,例如连接超时、读写操作超时等。通过设置合适的定时器,Muduo可以及时检测并处理超时情况,确保网络应用的稳定性和可靠性。
-
可能还不太全,后面再有所感悟再来更新。。。
本章完结
posted on 2024-01-18 16:50 LunarCod 阅读(15) 评论(0) 编辑 收藏 举报 来源
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通