使用timerfd实现定时器功能

2023-10-27

依旧以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 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_;
	// Timer list sorted by expiration
	TimerList 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 section
	for (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 list
			delete 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 please
		activeTimers_.erase(it);
	}
	else if (callingExpiredTimers_)
	{
		cancelingTimers_.insert(timer);
	}
	assert(timers_.size() == activeTimers_.size());
}

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

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用timerfd实现定时器功能 的相关文章

  • CTF show 萌新web-1

    首先看题目 代码整体逻辑是先通过GET请求传递参数id 如果id大于999 报错 如果小于999 则输出当前的执行的sql语句 如果根据id参数能在数据库中查到值 row 则将其打印出来 而flag则是id为1000的 row中的内容 这道
  • excel表格(.csv)保存到数据库--QT数据库

    刚学QT的菜鸟 无聊的时候总会找一点事情做 言归正传 将excel表格保存到数据库 是比较简单的 我的想法简单粗暴 从路径中获取文件 通过按键控制加载 将大象放进冰箱需要是三步 这个同样 1 获取路径 2 读取Excel文件 3 将其保存到
  • Domain-Specific Languages 23

    Domain Specific Languages Whenever you listen to a discussion by experts in any domain be it chess players kindergarten
  • 红日靶场(五)

    红日5 一 环境搭建 此次靶场虚拟机共用两个 一个外网一个内网 用来练习红队相关内容和方向 主要包括常规信息收集 Web攻防 代码审计 漏洞利用 内网渗透以及域渗透等相关内容学习 1 环境拓扑图 2 靶机下载地址 漏洞详情 3 我们设置VM
  • elasticsearch 集群配置

    主节点配置 集群名称 cluster name demo 节点名称 每个节点的名称不能重复 node name es node 1 ip 地址 每个节点的地址不能重复 network host 172 16 10 1 是不是有资格主节点 n
  • Python-schema的使用

    不管我们做什么应用 只要和用户输入打交道 就有一个原则 永远不要相信用户的输入数据 意味着我们要对用户输入进行严格的验证 web开发时一般输入数据都以JSON形式发送到后端API API要对输入数据做验证 一般我都是加很多判断 各种if 导
  • Python生成随机数,并将生成的随机数组成10道加减乘除的基本算术题目

    编写一个小学生算术能力测试题 提供10道加减乘除四种基本算术运算的题目 联系者根据显示的题目输入自己的答案 程序自动判断输入的答案是否正确并显示出相应的信息 生成一到一百的随机数 import random x random randint
  • SRM系统是什么?

    SRM全称Supplier Relationship Management 即供应商关系管理 SRM管理系统即供应商关系管理系统 供应商管理系统是采购管理系统的一个子系统 也是采购管理系统的一个重要模块 是用于改进企业与供应商关系的联系 完
  • Mysql主键约束和唯一约束

    Mysql约束 1 作用 约束定义为确保数据完整性必须遵循的规则 约束可以在创建表的过程中创建 也可以稍后再添加 在创建表后添加约束时 它将检查现有数据以确定其是否违背该约束 如果现有数据违背了将添加的约束 那么将不会向指定列施加该约束 2
  • 找不到文件、主类名和文件名不一致、缺少分号的解决方法

    1 找不到文件 解决方法 源文件名不存在或写错 或者当前路径错误 2 主类名和文件名不一致 解决方法 声明为public的主类应与文件名一致 否则编译失败 3 缺少分号 解决方法 编译失败 注意错误出现的行数 再到源代码中指定位置改错
  • shell实例流程控制&函数

    条件 if then elif then fi if的条件部分经常使用test EXPRESSION或 EXPRESSION 实现 test的用法可以参见test if 条件1 if 条件1 then then 执行语句1 elif 条件2
  • MetaMask安装使用指南

    前言 MetaMask是一个以太坊钱包插件 虽然只能在Chrome浏览器中使用 但作为以太坊钱包的metamask却很受以太坊开发者欢迎 MetaMask除了是一个简单的钱包 它主要卖点是让使用者可以很容易跟以太坊的智能合约互动 或者说说M
  • DLUT C++上机作业(实验六)

    注意 博客所有代码在VS上均能编译通过 codeblocks等编译器可能因为某些变量名无法识别而无法编译 我的VS上不能用end做变量名就很迷呀 2 有一个交通工具类vehicle 将它作为基类派生小车类car 卡车类truck和轮船类bo
  • Java面试必备,JVM核心知识点总结!

    JVM基础 程序计数器 Program Counter Register CPU中的寄存器 作用 记住下一条JVM指令 特点 线程私有 唯一一个不会出现内存溢出的区域 虚拟机栈 Java virtual mechine Stack 线程私有
  • 数据库查询: 列出表的所有字段,“*”符号,查询指定字段数据,DISTINCT查询,IN查询,BETWEEN AND查询,LIKE模糊查询,对查询结果排序,分组查询,统计分组查询

    数据库查询 列出表的所有字段 符号 查询指定字段数据 DISTINCT查询 IN查询 BETWEEN AND查询 LIKE模糊查询 对查询结果排序 分组查询 统计分组查询 列出表的所有字段 通过SQL语句SELECT列出表的所有字段 具体语
  • 软考-嵌入式系统设计师-笔记:嵌入式系统软件基础知识

    文章目录 嵌入式软件基础知识 嵌入式操作系统基础知识 任务调度 信号量 页面置换算法 嵌入式系统程序设计 嵌入式软件基础知识 嵌入式软件分类 系统软件 控制和管理嵌入式系统资源 为嵌入式应用提供支持的各种软件 如设备驱动程序 嵌入式操作系统
  • build中配置resource配置,来防止资源导出失败

  • 我最喜爱的十大技术文档写作工具

    转载 老实说 我爱死微软的Word了 Adobe FrameMaker也曾辉煌过 不过你懂的 这东西有时候会令人抓狂 过去5年来 我一直使用同一套写作工具 我也曾尝试过一些新的工具 可我最终还是很专情于我的老相好们 在这里我总结了一下我所用
  • MATLAB中GUI界面内数据的读取和存储操作

    要求GUI界面的输入数据为int16中频数据文件 输出数据也为int16中频数据文件 第一步 获取数据函数 uigetfile 先自己存储数据用于验证 将仿真数据以int16的格式存于txt文件中 分I O两路 I路代表实部 O路代表虚部

随机推荐

  • STM32f10x学习----ADC和DMA功能 后附具体操作及使用过程中遇到的问题

    学习某一个东西 我们首先要了解这个东西的定义是什么 用来干什么的 怎么用 用的过程中有什么注意事项 这些都OK了 那么我们就算是基本掌握他了 0 前言 ADC Analog to Digital Converter的缩写 指模 数转换器或者
  • PyTorch错误定位系列之CUDA error: device-side assert triggered

    PyTorch错误定位系列之CUDA error device side assert triggered Introduction 本栏目只是提供一些自己遇到的错误的解决思路 Background 我昨天写了个模型加了focal loss
  • 遗传算法GA优化BP神经网络(GA-BP)回归预测-Matlab代码实现

    一 前言 代码获取 评论区或者私信获取 遗传算法 Genetic Algorithm GA 和反向传播神经网络 Backpropagation Neural Network BPNN 都是常用的优化算法和模型 可以联合使用进行回归预测问题的
  • python多线程低效问题

    重点 Python由于有全锁局的存在 同一时间只能有一个线程执行 并不能利用多核优势 终于找到cpu利用率低的原因了 Python解释执行原理 我是一个Python线程 我的工作就是解释执行程序员编写的Python代码 之所以说是解释执行
  • 2018年Android最新面试题(一)

    最近在忙着找工作 所以趁热打铁写一份Android最新的面试题 希望可以帮助到大家 一直被问的问题Glide的源码 重点 最好和Picasso比较着说 Glide原理 自己看 https www jianshu com p 3d699bf0
  • APP过度索取问题严重,该如何有效解决?

    近几年移动应用市场发展快速 APP种类功能繁多 给人们的生活和工作带来了无限便捷 然而事物的发展必然有对立面 APP获取用户数据问题突出 同时加大了信息泄露的风险 工信部及各通信管理局等相关部门针对APP问题频频通报 使得移动应用开发商处于
  • 封闭岛屿数量 -- 二维矩阵的dfs算法

    1254 统计封闭岛屿的数目 这道题和 岛屿数量 二维矩阵的dfs算法 类似 区别在于不算边缘部分的岛屿 那其实很简单 把上 题中那些靠边的岛屿排除掉 剩下的就是 封闭岛屿 了 关于岛屿的相似题目 岛屿数量 二维矩阵的dfs算法 封闭岛屿数
  • openEuler 22.03-LTS 基础配置

    文章目录 1 设置语言环境 1 1 显示当前语言环境状态 1 2 列出可用的语言环境 1 3 设置语言环境 2 设置键盘 2 1 显示当前设置 2 2 列出可用的键盘布局 2 3 设置键盘布局 3 设置日期和时间 3 1 使用timedat
  • 聚合工程是什么?与微服务有什么区别和联系?

    1 聚合的概念 把项目的各个模块 子工程 聚合在一起构建 一般用于分模块开发 最后整体打包发布 Maven Project独立运行 Maven Module无法独立运行 2 聚合工程开发步骤 1 根项目是一个pom项目 2 子模块 Mave
  • Eigen 简单矩阵运算

    用到 Eigen Core 和 Eigen Dense 模块 矩阵定义 Eigen Matrix lt 数据类型 行数 列数 gt 矩阵名称 已经提供的矩阵类型 Vector3d 向量名称 实质上是 Eigen Matrix
  • Python 中 import 的机制与实现

    转自 http python jobbole com 82604 本文所涉及到的代码在github上 概述 Python 是一门优美简单 功能强大的动态语言 在刚刚接触这门语言时 我们会被其优美的格式 简洁的语法和无穷无尽的类库所震撼 在真
  • pandas简单学习(Spyder)

    1 导入Excel文件 data pd read excel D 下载 PlayTennis xlsx 2 查看数据维度 data7 data shape 3 查看数据类型 type data 4 索引 索引某一列 data1 data D
  • Linux之——添加VIP

    版权声明 本文为博主原创文章 未经博主允许不得转载 https blog csdn net l1028386804 article details 81347068 转载请注明出处 https blog csdn net l10283868
  • 使用VNA(Vector Network Analyzer)对S参数进行去嵌(二)

    使用VNA Vector Network Analyzer 对S参数进行去嵌 一 小孟boy的博客 CSDN博客 vna测s11公式 去嵌过程 无论是使用 EM 仿真工具创建的简化模型 如一段理想传输线 还是复杂模型用于测试夹具 现在都需要
  • Check failed: registry.count(type) == 1 (0 vs. 1) Unknown solver type: SGD (known types: )

    问题 在xcode下面编译调试caffe cpp时出现 Check failed registry count type 1 0 vs 1 Unknown solver type SGD known types 解决方法 在caffe cp
  • 4PCS、super4PCS粗配准算法理解

    参考了泡泡点云时空的文章4PCS点云粗配准算法介绍 一 4PCS系列的点云配准方法有点类似Ransac 通过找出目标点云和带配准点云中对应的两组点进行旋转平移求解出T 然后在众多的候选T中旋转一组最大重合的T 只是怎么找出对应点方法不一样
  • 关于深度学习中迭代次数iter和epoch等的关系

    1 在深度学习的训练中 epoch指的是所有的数据遍历了几次 而iter指的是整个batchsize输入到网络多少次 如果非得说关系 那 iter batchsize sum photos epoch
  • arch linux 安装教程(包括安装桌面环境,以及一些常用软件,输入法,网易云 等)

    2019 11 03添加 官方关于base组内所删除的包组情况 详细信息参照 wiki archlinux org 关于启动盘制作可以看 windows下安装grub2 可制作多功能U盘 和 grub2各种手动命令引导教程 这两篇文章 说明
  • java中执行js代码块_在Java中执行js代码

    Performance Monitor2 Peformance Counter Performance Counter 是量化系统状态或活动的一个数值 Windows Performance Monitor在一定时间间隔内 默认的取样间隔是
  • 使用timerfd实现定时器功能

    依旧以muduo为例 使用timerfd可以使用与socketfd类型相同的方式在IO复用中使用 使用timerfd create 创建一个timerfd 接着使用timerfd settime 设置定时器的到期时间 我们只需要注册time