Muduo源码笔记系列:
muduo源码阅读笔记(0、下载编译muduo)
muduo源码阅读笔记(1、同步日志)
muduo源码阅读笔记(2、对C语言原生的线程安全以及同步的API的封装)
muduo源码阅读笔记(3、线程和线程池的封装)
muduo源码阅读笔记(4、异步日志)
muduo源码阅读笔记(5、Channel和Poller)
muduo源码阅读笔记(6、EvevntLoop和Thread)
muduo源码阅读笔记(7、EventLoopThreadPool)
muduo源码阅读笔记(8、定时器TimerQueue)
muduo源码阅读笔记(9、TcpServer)
muduo源码阅读笔记(10、TcpConnection)
muduo源码阅读笔记(11、TcpClient)
前言
为了方便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 instead of raw pointers.
// This requires heterogeneous comparison lookup (N3465) from C++14
// so that we can find an T* in a set>.
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可以及时检测并处理超时情况,确保网络应用的稳定性和可靠性。
可能还不太全,后面再有所感悟再来更新。。。
本章完结