依旧以muduo为例。

使用timerfd可以使用与socketfd类型相同的方式在IO复用中使用。
使用timerfd_create()创建一个timerfd,接着使用timerfd_settime()设置定时器的到期时间。
我们只需要注册timerfd的可读事件,当定时器超时时,timerfd变成可读,调用其设置的可读的回调函数。

来看下TimerQueue.h

TimerQueue类定义

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 alarmsvoid handleRead();// move out all expired timersstd::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_;// Timer list sorted by expirationTimerList timers_;// for cancel()ActiveTimerSet activeTimers_;bool callingExpiredTimers_; /* atomic */ActiveTimerSet cancelingTimers_;
};

Timer类表示一个具体的定时任务,TimerId类标识某个定时任务,主要用来删除定时任务。
使用timers_作为任务队列来组织定时任务,TimerList即为std::set<Entry>Entry即为std::pair<Timestamp, Timer*>。这样设计的目的是为了使用std::set,同时又可以保存超时事件相同的定时任务。其实可以直接使用std::multimap<Timestamp, Timer*>,感觉这样会更方便。

成员timerfd_timer_create创建,成员timerfdChannel_用来监听timerfd_的可读事件,它的回调函数为TimerQueue::read()

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();
}

添加定时任务

使用接口TimerQueue::TimerId addTimer(TimerCallback cb, Timestamp when, double interval),当然EventLoop有更为明显的接口EventLoop::RunAt()EventLoop::RunAfter()EventLoop::RunEvery(),它们的实现都是调用TimerQueue::TimerId addTimer()`。

添加的任务的超时时间如果比之前最小的超时时间还小,需要重新设置定时器的超时时间,调用resetTimerfd()即可。

void resetTimerfd(int timerfd, Timestamp expiration)
{// wake up loop by timerfd_settime()struct itimerspec newValue;struct itimerspec oldValue;memZero(&newValue, sizeof newValue);memZero(&oldValue, sizeof oldValue);newValue.it_value = howMuchTimeFromNow(expiration);int ret = ::timerfd_settime(timerfd, 0, &newValue, &oldValue);if (ret){LOG_SYSERR << "timerfd_settime()";}
}

重新设置超时时间,不需要先取消原来的,再设置新的超时时间。直接调用timerfd_settime()即可。
这里有一个小的知识点需要注意,如果需要取消原来设置的定时任务,调用timerfd_settime()时,需要将传入的newValue都设置为0,
相当于调用memZero(&newValue, sizeof newValue),然后再调用timerfd_settime()即可。

定时器超时,执行任务

当定时器超时时,timerfd_变成可读,void TimerQueue::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 sectionfor (const Entry& it : expired){it.second->run();}callingExpiredTimers_ = false;reset(expired, now);
}

首先需要调用void readTimerfd(int timerfd, Timestamp now)读取timerfd_中的数据(必须取到64位的数据),否则之后将因为依旧可读再次调用readTimerfd(level triggered)

void readTimerfd(int timerfd, Timestamp now)
{uint64_t howmany;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";}
}

调用getExpired()获取超时的定时任务,返回时就已经将这些超时任务从任务队列[timers_]中删除,如果有任务时循环任务,将会在
void TimerQueue::reset(const std::vector<Entry>& expired, Timestamp now)重新添加到任务队列中。

std::vector<TimerQueue::Entry> TimerQueue::getExpired(Timestamp now)
{assert(timers_.size() == activeTimers_.size());std::vector<Entry> expired;Entry sentry(now, reinterpret_cast<Timer*>(UINTPTR_MAX));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 (const Entry& it : expired){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;
}

最后一步调用TimerQueue::reset(const std::vector<Entry>& expired, Timestamp now),对于这些已经超时的任务,如果任务为循环任务,需要重新将其加入到任务队列中。处理完之后,如果任务队列中仍有任务,取得任务中最近的超时时间作为定时器的超时时间重启定时器。

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 listdelete it.second; // FIXME: no delete please}}if (!timers_.empty()){nextExpire = timers_.begin()->second->expiration();}if (nextExpire.valid()){resetTimerfd(timerfd_, nextExpire);}
}

删除任务

使用接口void TimerQueue::cancel(TimerId timerId),它会转调void TimerQueue::cancelInLoop(TimerId timerId),其实现如下:

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()){size_t n = timers_.erase(Entry(it->first->expiration(), it->first));assert(n == 1); (void)n;delete it->first; // FIXME: no delete pleaseactiveTimers_.erase(it);}else if (callingExpiredTimers_){cancelingTimers_.insert(timer);}assert(timers_.size() == activeTimers_.size());
}

其实就是直接将该定时任务删除。

以前有个疑问,为什么删除时不处理待删除任务就是超时时间最短的定时任务这种特殊情况。
其实是不需要的,因为当以带产出任务的超时时间超时时,会去取已经超时的任务。而该任务已经从任务队列中删除,所以将不会取到该超时任务,前面已经说过,取消任务会调用timerfd_settime(),多了一个系统调用;不取消也没有什么影响。所以就采取了<不处理方式>。

使用timerfd实现定时器功能相关推荐

  1. python怎样编写定时程序_Python如何实现定时器功能

    Timer: 隔一定时间调用一个函数,如果想实现每隔一段时间就调用一个函数的话,就要在Timer调用的函数中,再次设置Timer.Timer是Thread的一个派生类 python中的线程提供了jav ...

  2. SysTick系统定时器(功能框图和优先级配置)

    SysTick系统定时器(功能框图和优先级配置) SysTick-系统定时器是属于 CM3 内核中的一个外设,内嵌在 NVIC 中.系统定时器是一个 24bit (2^24)的向下递减的计数器,计数器 ...

  3. C++最普通的定时器功能实现

    由于简单测试,就实现一个最简单的定时器功能 头文件: #pragma once #include <iostream> #include <string> #include & ...

  4. STM32F407核心板定时器功能引脚分配

    STM32F407 定时器功能引脚分配

  5. STM32定时器功能概括

    定时器分类 不同的芯片定时器的个数也是不同的,以STM32F103ZE有8个定时器(定时器的具体个数查相关手册). 定时器的分类:高级定时器.通用定时器.基本定时器,这3类定时器的功能各不相同. 定时 ...

  6. 浅谈一下单片机的定时器功能

    MCU相当于一个微控制器,与其他芯片相比,最大的特点是它的可编程特性.由于其可编程功能可以广泛应用于生活的各个方面,如手机.PC周边设备.遥控器.汽车.电子.智能家居等.但这些都是使用具有不同电路的M ...

  7. Arduino ESP32定时器功能使用

    Arduino ESP32定时器功能使用 ESP32硬件定时器介绍 ESP32 芯片包含两个硬件定时器组.每组有两个通用硬件定时器.它们都是基于 16 位预分频器和 64 位自动重载功能的向上/向下计 ...

  8. MM32F3277 MicroPython 的定时器功能

    简 介: 对于SuYong发送过来的带有Timer功能版本的MicroPython进行了测试.在新版的MicroPython中,可以最多定义两个不同频率的定时器中断,完成对于周期时间的控制和输出.这一 ...

  9. 杂乱无序的时间轮:有效实现定时器功能的数据结构

    <实现较低的计时器粒度以重传TCP(RTO):时间轮算法如何减少开销> <分级时间轮优化普通时间轮定时器> <分级时间轮优化普通时间轮定时器(2):滴答式分层计时轮> ...

  10. Linux下 timerfd创建定时器并使用 epoll 监听

    介绍:Linux系统提供了timerfd系列的定时函数,其具体函数名如下 #include <sys/timerfd.h>int timerfd_create(int clockid, i ...

最新文章

  1. Java基础(二) 程序格式
  2. 【研究院】滴滴研究院,都在做什么
  3. 6678EVM调试K1_STK_v1.1例程中GE_test的时候报错:DDR3 leveling has failed, STATUS = 0x40000064的解决办法
  4. MySQL5.6二进制软件包编译安装详解(三)
  5. 1分钟了解基于内容的推荐,pm又懂了
  6. mysql 汉编码 的选_peewee连接mysql汉语言数据编码_mysql
  7. aes c语言 逆列混合函数,c语言aes列混合和逆列混合的实现(3页)-原创力文档
  8. 管理数据库计算机网络,计算机网络与数据库管理系统.pdf
  9. 说透Applet的数字签名之2——数字签名
  10. linux i3 桌面,Linux安装i3wm平铺式窗口桌面
  11. 投影仪与计算机连接方式,电脑怎么接投影仪教程 简单三步教你搞定
  12. 计算机电源 3842,UC3842开关电源保护的几个技巧及电路图
  13. 【JAVA】数据结构——二叉树 例题练习及代码详解
  14. linux 断开远程vnc,Linux停VNC远程控制的使用方法
  15. FastText在商品分类下的应用(第十届服创大赛全国三等奖)
  16. matlab r2021b校园正版软件安装过程的问题及解决方案记录
  17. 创建一个urdf机器人_ROS机器人Diego 1#制作(十六)创建机器人的urdf模型描述文件详解...
  18. Zynga以特别的《CSR Racing 2》系列活动庆祝布加迪110周年
  19. JQuery fadeOut()函数与remove()函数合用时不生效的解决办法
  20. 这2个PDF转Word免费不限页数工具很多人没用过

热门文章

  1. ES6 Symbol之浅入解读
  2. datagrid--新增
  3. PHP面向对象深入研究之【对象生成】
  4. JQuery Lightbox -- 一个简单而又谦恭的用来把图片覆盖在当前页面上的脚本
  5. 高度设置为100%无效的解决办法
  6. 1.深入分布式缓存:从原理到实践 --- 缓存为王
  7. 4.Linux/Unix 系统编程手册(上) -- 文件IO:通用的IO模型
  8. 1.卷1(套接字联网API)---简介
  9. 45. 将脚本放在底部(6)
  10. 105.输出控制缓存