浅析muduo库中的定时器设施

一个设计良好的定时器在服务端的应用程序上至关重要,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是否有出现的必要?

原文地址:https://www.cnblogs.com/ukernel/p/9191107.html

时间: 2024-11-06 12:00:48

浅析muduo库中的定时器设施的相关文章

浅析muduo库中的线程设施

muduo是目前我在学习过程中遇到的最具有学习意义的网络库,下文将分析muduo库中的基础设施--Thread和ThreadPool. 首先,介绍在多线程编程中不可缺少的同步措施--Mutex和Condition. Mutex ``` /Mutex.h/ class MutexLock : boost::noncopyable { public: MutexLock() : holder_(0) { MCHECK(pthread_mutex_init(&mutex_, NULL));//MCHE

muduo库中TcpServer一次完整的工作流程

模拟单线程情况下muduo库的工作情况 muduo的源代码对于一个初学者来说还是有一些复杂的,其中有很多的回调函数以及交叉的组件,下面我将追踪一次TCP连接过程中发生的事情,不会出现用户态的源码,都是库内部的运行机制.下文笔者将描述一次连接发生的过程,将Channel到加入到loop循环为止. 监听套接字加入loop循环的完整过程 首先创建一个TcpServer对象,在的创建过程中,首先new出来自己的核心组件(Acceptor,loop,connectionMap,threadPool)之后T

muduo库中的核心:std::bind和std::function

最近在读完陈硕大牛的<Linux多线程服务端编程>以及muduo源码后,对其中的一些实现细节有着十分深刻的印象,尤其是使用std::bind和std::function的回调技术.可以说,这两个大杀器简直就是现代C++的"任督二脉",甚至可以解决继承时的虚函数指代不清的问题.在此详细叙述使用std::bind和std::function在C++对象之间的用法,用以配合解决事件驱动的编程模型.笔者才疏学浅,如果解释的不正确希望朋友们不吝赐教. 下面的所有讨论基于对象. std

Android中实现定时器的3中方法

在Android开发中,定时器一般有以下3种实现方法: 一.采用Handler与线程的sleep(long)方法: 二.采用Handler的postDelayed(Runnable, long)方法: 三.采用Handler与timer及TimerTask结合的方法: 一.采用Handle与线程的sleep(long)方法 Handler主要用来处理接受到的消息.这只是最主要的方法,当然Handler里还有其他的方法供实现,有兴趣的可以去查API,这里不过多解释. 1. 定义一个Handler类

浅析Thinkphp框架中运用phprpc扩展模式

浅析Thinkphp框架中应用phprpc扩展模式 这次的项目舍弃了原来使用Axis2做web服务端的 方案,改用phprpc实现,其一是服务端的thinkphp已集成有该模式接口,其二是phprpc传输的数据流相对于普通WebService中的 XML或JSON形式的数据量明显减少,而且因为数据量的关系解析速度明显比较快~~ 说实话,以前还真不知道有phprpc这个协议的,本打算使用 sina的api的restlet形式开发,但自己写库的话会花比较多的时间,而现在轻量级的php框架支持rest

结构体在固件库中的应用

上次介绍了一般结构体的定义以及引用方法,那么接下来将对结构体在官方固件库是如何具体使用的做出简单说明. 结构体指针成员变量引用方法是通过“→”符号来实现,比如要访问student1结构体指针指向的结构体的成员变量name,那么方法是: stuednt1—>name; 如在STM32官方固件库中对端口使用模式结构体定义如下: typedef enum { GPIO_Mode_AIN = 0x0, //模拟输入模式 GPIO_Mode_IN_FLOATING = 0x04, //浮空输入模式 GPI

CAML获取SharePoint文档库中除文件夹外所有文档

方法一: ? 1 2 3 4 <QueryOptions>         <ViewAttributes Scope="Recursive" />     </QueryOptions> </query> 方法二: ? 1 2 3 4 5 <View Scope="RecursiveAll">     <Query>         <Where>...</Where>

MySQL生产库中添加修改表字段引起主从崩溃的问题总结

上周末和开发人员对线上库中的部分表的在线DDL和update,这过程中出现了一些意料之外的问题,现将过程.分析和解决方案在这里总结一下 一. 需求背景: 要在如下表中添加字段(modified_at)并且更改默认值 table_name { baby_compbaby_comp_statusbaby_usrbaby_ad_userbaby_campbaby_ordbaby_acc_eva } 每张表执行如下操作ALTER TABLE `$table_name` ADD COLUMN `modif

Boost库中shared_ptr(上)

1.共享性智能指针(shared_ptr) 引用计数型指针 shared_ptr是一个最像指针的"智能指针",是boost.smart_ptr库中最有价值,最重要,也是最有用的.  shared_ptr实现的是引用技术型的智能指针,可以被拷贝和赋值,在任意地方共享它,当没有代码使用(此时引用         计数为0)它才删除被动态分配的对象.shared_ptr也可以被安全的放到标准容器中: 2.怎么使用shared_ptr 举一个操作的例子: #include<iostrea