高性能定时器介绍及代码逐行解析--时间堆
简介
在《Linux高性能服务器编程》中,介绍了三种定时方法:
- socket选项SO_RCVTIMEO和SO_SNDTIMEO
- SIGALRM信号
- I/O复用系统调用的超时参数
基础知识
- 非活跃,是指客户端(这里是浏览器)与服务器端建立连接后,长时间不交换数据,一直占用服务器端的文件描述符,导致连接资源的浪费。
- 定时事件,是指固定一段时间之后触发某段代码,由该段代码处理一个事件,如从内核事件表删除事件,并关闭文件描述符,释放连接资源。
- 定时器,是指利用结构体或其他形式,将多种定时事件进行封装起来。具体的,这里只涉及一种定时事件,即定期检测非活跃连接,这里将该定时事件与连接资源封装为一个结构体定时器。
- 定时器容器,是指使用某种容器类数据结构,将上述多个定时器组合起来,便于对定时事件统一管理。具体的,项目中使用升序链表将所有定时器串联组织起来。
在web服务中,如果某一用户connect()到服务器之后,长时间不交换数据,一直占用服务器端的文件描述符,导致连接资源的浪费。这时候就应该利用定时器把这些超时的非活动连接释放掉,关闭其占用的文件描述符。这种情况也很常见,当你登录一个网站后长时间没有操作该网站的网页,再次访问的时候你会发现需要重新登录。
时间堆原理
将所有定时器中超时时间最小的一个定时器的超时值作为心搏间隔,这样,一旦心搏函数tick被调用,到期的一定是超时时间最小的时间器。然后,再从剩下的定时器中找出超时时间最小的那个座位心搏间隔,如此反复直至定时器执行完。
从上面的运行过程我们可以看出,最小堆很适合这种定时方案。
但是使用最小堆虽然可以很方便的取得当前时间最小的时间器,但是却不支持随机访问,非常不便于维护连接的过期时间。由于最小堆是一种完全二叉树,所以我们可以用数组来组织其中的元素。
对于数组中的任意一个位置i上的元素,其作儿子节点在位置2i+1上,右儿子节点在位置2i+2上,父节点在位置「(i-1)/2」上。用数组来表示堆不仅节省空间,而且更容易实现堆的插入、删除等操作。
那么如何维护这个数组使它具有最小堆堆性质呢?假设我们已经有一个包含N个元素的数组,现在要把它初始化为一个最小堆。我们可以将元素挨个插入数组中,但是这样效率偏低。正确的做法是:对数组的第「(N-1)/2」~0个元素执行下虑操作,即可保证该数组构成一个最小堆。这是因为包含N个元素的完全二叉树有「(N-1)/2」个非叶节点,这些非叶节点正是该完全二叉树的第0~「(N-1)/2」个节点。我们只需要确保这些非叶子节点构成的子树都具有堆序性质即可。
用数组模拟堆的算法代码(向上调整、向下调整等)见此:《敬请期待。。》
代码实现
// heaptimer.h
#ifndef HEAP_TIMER_H
#define HEAP_TIMER_H#include <queue>
#include <unordered_map>
#include <time.h>
#include <algorithm>
#include <arpa/inet.h>
#include <functional>
#include <assert.h>
#include <chrono>
#include "../log/log.h"typedef std::function<void()> TimeoutCallBack;
typedef std::chrono::high_resolution_clock Clock;
typedef std::chrono::milliseconds MS;
typedef Clock::time_point TimeStamp;struct TimerNode { // 使用结构体作为时间器int id; // 每个定时器所唯一的TimeStamp expires; // 设置该定时器的过期时间TimeoutCallBack cb; // 当超时时执行回调函数,用来关闭过期连接// 由于需要维护最小堆,所以需要重载比较运算符,方便定时器之间的比较bool operator<(const TimerNode& t) {return expires < t.expires;}
};class HeapTimer { // 管理所有的定时器的堆
public:HeapTimer() { heap_.reserve(64); }~HeapTimer() { clear(); }// 调整指定id的结点void adjust(int id, int newExpires);// 添加一个定时器void add(int id, int timeOut, const TimeoutCallBack& cb);// 删除指定id的节点,并执行其中的回调函数void doWork(int id);void clear();// 处理过期的定时器void tick();// 清除idx = 0的定时器void pop();// 下一次处理过期定时器的时间int GetNextTick();private:// 直接操作时间堆的函数不需要暴露给用户// 删除数组下标为i处的定时器void del_(size_t i);// 向上调整void siftup_(size_t i);// 向下调整bool siftdown_(size_t index, size_t n);// 交换节点void SwapNode_(size_t i, size_t j);// 使用vector来模拟最小堆,里面存的类型是前面定义的时间器std::vector<TimerNode> heap_; // map中存储的信息为<时间器的id,该时间器在vector中的数组下标>std::unordered_map<int, size_t> ref_;
};#endif //HEAP_TIMER_H
#include "heaptimer.h"void HeapTimer::siftup_(size_t i) {assert(i >= 0 && i < heap_.size());size_t j = (i - 1) / 2;while(j >= 0) {if(heap_[j] < heap_[i]) { break; }SwapNode_(i, j);i = j;j = (i - 1) / 2;}
}void HeapTimer::SwapNode_(size_t i, size_t j) {assert(i >= 0 && i < heap_.size());assert(j >= 0 && j < heap_.size());std::swap(heap_[i], heap_[j]);ref_[heap_[i].id] = i;ref_[heap_[j].id] = j;
} bool HeapTimer::siftdown_(size_t index, size_t n) {assert(index >= 0 && index < heap_.size());assert(n >= 0 && n <= heap_.size());size_t i = index;size_t j = i * 2 + 1;while(j < n) {if(j + 1 < n && heap_[j + 1] < heap_[j]) j++;if(heap_[i] < heap_[j]) break;SwapNode_(i, j);i = j;j = i * 2 + 1;}return i > index;
}void HeapTimer::add(int id, int timeout, const TimeoutCallBack& cb) {assert(id >= 0);size_t i;if(ref_.count(id) == 0) {/* 新节点:堆尾插入,调整堆 */i = heap_.size(); ref_[id] = i; // map更新heap_.push_back({id, Clock::now() + MS(timeout), cb}); // 将新节点插入堆中siftup_(i); // 此时新节点为叶节点,向上维护} else {/* 已有结点:调整堆 */i = ref_[id];heap_[i].expires = Clock::now() + MS(timeout); // 更新其超时时间heap_[i].cb = cb;if(!siftdown_(i, heap_.size())) {siftup_(i);}}
}void HeapTimer::doWork(int id) {/* 删除指定id结点,并触发回调函数 */if(heap_.empty() || ref_.count(id) == 0) {return;}size_t i = ref_[id];TimerNode node = heap_[i];node.cb(); // 执行其该定时器的任务del_(i); // 执行结束将定时器删除
}void HeapTimer::del_(size_t index) {/* 删除指定位置的结点 */assert(!heap_.empty() && index >= 0 && index < heap_.size());/* 将要删除的结点换到队尾,然后调整堆 */size_t i = index;size_t n = heap_.size() - 1;assert(i <= n);if(i < n) {SwapNode_(i, n);if(!siftdown_(i, n)) {siftup_(i);}}/* 队尾元素删除 */ref_.erase(heap_.back().id);heap_.pop_back();
}void HeapTimer::adjust(int id, int timeout) {/* 调整指定id的结点 */assert(!heap_.empty() && ref_.count(id) > 0);heap_[ref_[id]].expires = Clock::now() + MS(timeout);;siftdown_(ref_[id], heap_.size());
}void HeapTimer::tick() {/* 清除超时结点 */if(heap_.empty()) {return;}while(!heap_.empty()) {TimerNode node = heap_.front();if(std::chrono::duration_cast<MS>(node.expires - Clock::now()).count() > 0) { break; }node.cb();pop();}
}void HeapTimer::pop() {assert(!heap_.empty());del_(0);
}void HeapTimer::clear() {ref_.clear();heap_.clear();
}int HeapTimer::GetNextTick() {tick();size_t res = -1;if(!heap_.empty()) {res = std::chrono::duration_cast<MS>(heap_.front().expires - Clock::now()).count();if(res < 0) { res = 0; }}return res;
}
相关文章:
高性能定时器介绍及代码逐行解析--时间堆
简介 在《Linux高性能服务器编程》中,介绍了三种定时方法: socket选项SO_RCVTIMEO和SO_SNDTIMEOSIGALRM信号I/O复用系统调用的超时参数 基础知识 非活跃,是指客户端(这里是浏览器)与服务器端建立连接后,…...
汇编语言学习笔记五
div指令 除法, 被除数:默认是放在ax或者dx中,其位数为16位,则在ax中,如位数为32位,则高位在dx中,低位在ax中 除数:放在寄存器或者内存单元中,有8位和16位两种。 结果&am…...
Linux下的epf 是什么?
EPF (Extended Page Frame) 是 Linux 内核中的一个功能,它用于管理大内存系统中的物理页框。具体来说,当系统中的物理内存超过 1TB 时,传统的页表结构会变得非常庞大和复杂,给内存管理带来很大的困难。 EPF 架构通过将物理地址分…...
如何在广告形式选择上化解用户厌恶和变现瓶颈?
用户讨厌广告,这似乎是一个共识。在日复一日的使用中,用户会遇到各种各样的广告形式,从搜索结果中的广告链接,到视频中不间断的广告,再到流行应用中的推广内容。 无处不在的广告已经让用户不胜其烦,这也…...
【Android入门到项目实战-- 9.2】—— 传感器实战使用教程(靠近黑屏和计步器)
上篇文章介绍了传感器的基础用法(如有需要,可先移步),下面将通过两个实战案例学习具体如何使用。 一、靠近黑屏 这是距离传感器的简单应用。 –检测手机是否贴在耳朵上正在打电话,以便自动熄灭屏幕达到省电的目的。也…...
软件项目生命周期模型
目录 瀑布模型 快速原型模型 敏捷模型 迭代模型(增量模型) 螺旋模型 瀑布模型 定义:早就计划好了,按计划顺序(计划、设计、开发、测试、维护)线性执行 适用于:需求明确、变化少的项目 缺…...
linux系统TP-ti,tsc2046外设调试
一、整体调试思路 tp外设属于比较常见且比较简单的外设,今天以ti,tsc2046这款为例简述下tp外设的调试。 整体思路 1、配置设备树----驱动调试的device部分 2、tp驱动编译及匹配—driver部分 3、驱动整体调试 二、配置设备树 对于ti,tsc2046我们可以参考内核Docum…...
ChatGPT指令大全
1. 写报告:我现在正在 [报告的情境与目的]。我的简报主题是 [主题],请提供 [数字] 种开头方式,要简单到 [目标族群] 能听懂,同时要足够能吸引人,让他们愿意专心听下去。 2. 研究报告:写出一篇有关 [知识] …...
【Vue面试题】Vue2.x生命周期?
文章目录 1.有哪些生命周期(系统自带)?beforeCreate( 创建前 )created ( 创建后)beforeMount (挂载前)mount (挂载后)beforeUpdate (更新前)updated (更新后)beforeDestroy(销毁前)destroy(销毁后…...
运算放大器 - 笔记 02 -恒流源
恒流源 / 电流源 一、方案一二、方案二三、方案三四、方案四 前言:最近在学习运放,三极管,二极管,场效应管等器件的组合电路。捡起了以前的模电知识,写下笔记,以防再度忘记。 本文使用Multisim仿真软件进行…...
Python:Python进阶:Python字符串驻留技术
Python字符串驻留技术 1.什么是字符串驻留2. 为什么要驻留字符串3. Python的字符串驻留4. Python 字符驻留原理4.1 如何驻留字符串4.2 如何清理驻留的字符串 5. 字符串驻留的实现5.1. 变量、常量与函数名5.2 字典的键5.3 任何对象的属性5.4 显式地驻留 6 字符串驻留的其他发现 …...
2022年 全国职业院校技能大赛(中职组)网络安全赛项 正式赛卷 A模块 做题记录
评分标准文件及环境 评分标准:ZZ-2022029 网络安全赛项正式赛卷.zip 自己做的Linux靶机: 自己做的Windows靶机: 文章目录 评分标准文件及环境A-1 任务一 登录安全加固1. 密码策略(Windows,Linux)a. 最小密…...
华为OD机试 - 优选核酸检测点(Python)
题目描述 张三要去外地出差,需要做核酸,需要在指定时间点前做完核酸,请帮他找到满足条件的核酸检测点。 给出一组核酸检测点的距离和每个核酸检测点当前的人数给出张三要去做核酸的出发时间 出发时间是10分钟的倍数,同时给出张三做核酸的最晚结束时间题目中给出的距离是整…...
windows怎么把包含某个关键词的文件移动到一个文件夹中
文章目录 windows怎么把包含某个关键词的文件移动到一个文件夹中问题来源省流版本操作过程具体问题方法一:使用cmd终端解决方法二:使用python脚本 总结 windows怎么把包含某个关键词的文件移动到一个文件夹中 问题来源 今天想移动window文件࿰…...
Unity 后处理(Post-Processing) -- (2)创建后处理配置文件
通过前面一小节,我们初步认识了后处理是什么,在Unity中简单的试了试后处理的效果。本节我们来创建一个我们自己的后处理配置文件(post-processing profile)。 一个后处理配置文件包含了一系列为了达到特定视觉效果的后处理效果的配…...
BI 商业智能和报表,傻傻分不清楚?一文给你讲透
我们经常所听到的大数据、商业智能BI、数据分析、数据挖掘等我们都统称为数据信息化。数据信息化可以帮助企业全面的了解企业的经营管理,从经验驱动到数据驱动,降低情绪、心理等主观影响,形成以数据为基础的业务决策支撑,提高决策…...
CSS布局基础(传统布局小结)
传统布局小结 传统布局方式标准流浮动流定位伪类元素CSS应用对象应用到自身应用到其他元素 传统布局方式 传统布局采用 标准流 浮动流 定位的方式实现布局效果,也就是通常所说的 DIV CSS 布局。 标准流 标准流中的元素在 页面默认的 维度,块级元素…...
【五一创作】Qt quick基础1(包含基本元素Text Image Rectangle的使用)
Qt quick基础1(包含基本元素Text Image Rectangle的使用) 目录 Qt quick基础1(包含基本元素Text Image Rectangle的使用)前言qt中有直接设计ui的拖拽式的widget,为什么还需要Qtquick?QML语言Qt 版本创建一个Qt quick项…...
LVS+Keepalived 高可用群集部署
一、LVSKeepalived 高可用群集 在这个高度信息化的 IT 时代,企业的生产系统、业务运营、销售和支持,以及日常管理等环节越来越依赖于计算机信息和服务,对高可用(HA)技术的应用需求不断提高,以便提供持续的…...
小黑子—Java从入门到入土过程:第八章
Java零基础入门8.0 Java系列第八章1. 双列集合 Map1.1 Map 集合中常见的API1.2 Map 集合的遍历方式1.2 - I 第一种遍历方式:键找值KeySet 方法1.2 - II 第二种遍历方式:键值对 entrySet 方法1.2 - III 第三种遍历方式:lambda表达式 1.3 HashM…...
innodb_flush_log_at_trx_commit 和 sync_binlog 参数解析
这两个参数和MySQL的一致性以及性能相关,默认配置大多数情况下不是最优的。一般来说,互联网线上系统的配置: innodb_flush_log_at_trx_commit —— 0 sync_binlog —— 1000 一、innodb_flush_log_at_trx_commit 事务提交刷盘时机 如果我…...
hd debug - DAPLink的资料
文章目录 DAPLink的资料概述笔记库迁出的技巧END DAPLink的资料 概述 查资料时, 看到有DAPLink的资料, 记录一下. 笔记 DAPLink项目分为软件和硬件2部分, 不在一个库中. 总览 : https://daplink.io/ 这个页面上说了软件和硬件项目的库地址. 软件库地址 : https://github.…...
Android adb常用50条命令
1. adb devices - 列出所有连接的 Android 设备及模拟器 2. adb shell - 启动 Android 设备或模拟器的 shell 终端 3. adb install - 安装 APK 文件 4. adb uninstall - 卸载 APK 文件 5. adb logcat - 查看日志输出信息,用于调试应用 6. adb push - 将文件推送到 Andro…...
【无人车】无人驾驶地面车辆避障研究(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
Visual Studio高效调试手段与调试技巧总结
目录 1、对0xCCCCCCCC、0xCDCDCDCD和0xFEEEFEEE等常见异常值的辨识度 2、在Debug下遇到报错弹框,点击重试,查看函数调用堆栈...
Day37 Map集合
Map集合 Map集合是接口,interface Map <K , V> K:键的类型; V:值的类型 将键映射到值得对象;不能包含重复的键;每个键可以映射到最多一个值。例如:001 令狐冲 ; 002 岳不群 ; …...
是人就能学会的Spring源码教学-Spring的简单使用
是人就能学会的Spring源码教学-Spring的简单使用 Spring的最简单入门使用第一步 创建项目第二步 配置项目第三步 启动项目 Spring的最简单入门使用 各位道友且跟我一道来学习Spring的最简单的入门使用,为了方便和简单,我使用了Spring Boot项目ÿ…...
NOC大赛·核桃编程马拉松赛道知识点大纲(高年级及初中组)
NOC核桃编程马拉松知识点大纲(高年级及初中组) (一)基础语法 1.掌握运动积木的用法。 包括“移动 10 步”、“左/右转 X 度”、“面向 X 方向/鼠标指针/ 角色”、“移到 XY 坐标/鼠标/角色”、“X/Y 坐标的设定和增加”、 “滑行到 XY/鼠标/角色”等积木用法,详细如下。 1…...
第二十六章 Unity碰撞体Collision(上)
在游戏世界中,游戏物体之间的交互都是通过“碰撞接触”来进行交互的。例如,攻击怪物则是主角与怪物的碰撞,触发机关则是主角与机关的碰撞。在DirectX课程中,我们也大致介绍过有关碰撞检测的内容。游戏世界中的3D模型的形状是非常复…...
Qt Installer Framework使用教程:
步骤一: 下载并安装Qt Installer Framework工具 http://download.qt.io/official_releases/qt-installer-framework/ 将安装目录添加到环境变量,如安装D盘时D:\Qt\QtIFW-4.5.0\bin 步骤二: 将测试例子(如D:\Qt\QtIFW-4.5.0\…...
做爰的最好看的视频的网站/女排联赛排名
1.选择控制面板----> 选择 程序 ————> 选择 打开或关闭windows功能2.此时回弹出一个对话框,将telnet客户端打上√注意:不要讲telnet服务端打上√转载于:https://blog.51cto.com/linux2585/1540136...
桂林象鼻山门票/深圳优化公司哪家好
“Gotham” by James Gilleard♚作者:Nugine专栏地址:zhuanlan.zhihu.com/c_168195059在本篇文章中,我要向你展示使用 Cython 扩展 Python 的技巧。如果你同时有 C/C和 Python 的编码能力,我相信你会喜欢这个的。我们要造的轮子是…...
建设网站报告书/免费推广引流平台
我有些尴尬地拿着水杯,正对面坐着来访的王总,他是在别处打拼的人,这几年据说收获颇丰,见移动互联网如火如荼,自然也想着要进来干一场,尽管王总从事的行当也算跟IT沾边,但毕竟太长时间不接触技术…...
网站建设的合同书/网络营销策略制定
一、前言为了方便小公司没有运维开发人员,利用Jenkin解决了繁琐的打包部署问题。这次我就写了一个Gogs的集成教程,我觉的Gogs私服比较简单,其他的GitLab、svn、GitHub基本上也是一样的,搭建好了,开发人员只需要提交到版…...
网站域名被劫持/西安seo霸屏
到目前为止我们所接触过的角色都是可以接收消息的,而消息的类型也是五花八门,如String、元组、case类/自定义消息等。然而发送消息的行为在感觉上与我们日常编程工作中所使用的常规函数调用还是有很大区别的,为了弥合二者之间的鸿沟ÿ…...
网站制作公司多少人/郑州做网站最好的公司
需求: 从网上下载的N张.png图片保存到image目录中,将下载下来的图片全部重命名test1.png/test2.png... 实现代码: 目录结构: config-->setting.py #!/usr/bin/env python # -*- coding: utf-8 -*- __author__ tian __data__ …...