基于libco的c++协程实现(时间轮定时器)
在后端的开发中,定时器有很广泛的应用。
比如:
心跳检测
倒计时
游戏开发的技能冷却
redis的键值的有效期等等,都会使用到定时器。
定时器的实现数据结构选择
红黑树
对于增删查,时间复杂度为O(logn),对于红黑树最⼩节点为最左侧节点,时间复杂度O(logn)
最小堆
对于增查,时间复杂度为O(logn),对于删时间复杂度为O(n),但是可以通过辅助数据结构( map 或者hashtable来快速索引节点)来加快删除操作;对于最⼩节点为根节点,时间复杂度为O(1)
跳表
对于增删查,时间复杂度为O(logn),对于跳表最⼩节点为最左侧节点,时间复杂度为O(1),但是空间复杂度⽐较⾼,为O(1.5n)
时间轮
对于增删查,时间复杂度为O(1),查找最⼩节点也为O(1)
libco的使用了时间轮的实现
首先,时间轮有几个结构,必须理清他们的关系。
struct stTimeoutItem_t
{enum { eMaxTimeout = 40 * 1000 }; // 40sstTimeoutItem_t* pPrev; // 前stTimeoutItem_t* pNext; // 后stTimeoutItemLink_t* pLink; // 链表,没有用到,写这里有毛用OnPreparePfn_t pfnPrepare; // 不是超时的事件的处理函数OnProcessPfn_t pfnProcess; // resume协程回调函数void* pArg; // routine 协程对象指针bool bTimeout; // 是否超时unsigned long long ullExpireTime; // 到期时间
};struct stPoll_t;
struct stPollItem_t : public stTimeoutItem_t
{struct pollfd* pSelf; // 对应的poll结构stPoll_t* pPoll; // 所属的stPoll_tstruct epoll_event stEvent; // epoll事件,poll转换过来的
};// co_poll_inner 创建,管理这多个stPollItem_t
struct stPoll_t : public stTimeoutItem_t
{struct pollfd* fds; // poll 的fd集合nfds_t nfds; // poll 事件个数stPollItem_t* pPollItems; // 要加入epoll 事件int iAllEventDetach; // 如果处理过该对象的子项目pPollItems,赋值为1int iEpollFd; // epoll fd句柄int iRaiseCnt; // 此次触发的事件数
};
我把这几个结构拉一起了,
其中,能看出,stCoEpool_t管理了这一切
// TimeoutItem的链表
struct stTimeoutItemLink_t
{stTimeoutItem_t* head;stTimeoutItem_t* tail;
};// TimeOut
struct stTimeout_t // 时间伦
{stTimeoutItemLink_t* pItems; // 时间轮链表,开始初始化分配只一圈的长度,后续直接使用int iItemSize; // 超时链表中一圈的tick 60*1000unsigned long long ullStart; // 时间轮开始时间,会一直变化long long llStartIdx; // 时间轮开始的下标,会一直变化
};// epoll 结构
struct stCoEpoll_t
{int iEpollFd;static const int _EPOLL_SIZE = 1024 * 10;struct stTimeout_t* pTimeout; // epoll 存着时间轮struct stTimeoutItemLink_t* pstTimeoutList; // 超时事件链表struct stTimeoutItemLink_t* pstActiveList; // 用于signal时会插入co_epoll_res* result;
};
也就是说,一个协程,就有一个,在co_init_curr_thread_env 中创建
它管理着超时链表,信号事件链表
其中的pTimeout,就是时间轮,也就是一个数组,这个数组的大小位60*1000
stTimeout_t *AllocTimeout( int iSize )
{stTimeout_t *lp = (stTimeout_t*)calloc( 1,sizeof(stTimeout_t) );lp->iItemSize = iSize;// 注意这里先把item分配好了,后续直接使用lp->pItems = (stTimeoutItemLink_t*)calloc( 1, sizeof(stTimeoutItemLink_t) * lp->iItemSize );lp->ullStart = GetTickMS();lp->llStartIdx = 0;return lp;
}
这就是分配的时间轮的方法,首先指定了下标时间等信息,根据结构注释应该不难懂
相关视频推荐
红黑树、最小堆、时间轮、跳表多种方式实现定时器
海量定时任务设计-时间轮
2023年最新技术图谱,c++后端的8个技术维度,助力你快速成为大牛
免费学习地址:c/c++ linux服务器开发/后台架构师
需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享
有了这些后,再来看看时怎么添加超时事件的
// apTimeout:时间轮
// apItem: 某一个定时item
// allNow:当前的时间
// 函数目的,将超时项apItem加入到apTimeout
int AddTimeout( stTimeout_t *apTimeout, stTimeoutItem_t *apItem ,unsigned long long allNow )
{// 这个判断有点多余,start正常已经分配了if( apTimeout->ullStart == 0 ){apTimeout->ullStart = allNow;apTimeout->llStartIdx = 0;}// 当前时间也不大可能比前面的时间大if( allNow < apTimeout->ullStart ){co_log_err("CO_ERR: AddTimeout line %d allNow %llu apTimeout->ullStart %llu",__LINE__,allNow,apTimeout->ullStart);return __LINE__;}if( apItem->ullExpireTime < allNow ){co_log_err("CO_ERR: AddTimeout line %d apItem->ullExpireTime %llu allNow %llu apTimeout->ullStart %llu",__LINE__,apItem->ullExpireTime,allNow,apTimeout->ullStart);return __LINE__;}// 到期时间到start的时间差unsigned long long diff = apItem->ullExpireTime - apTimeout->ullStart;// itemsize 实际上是毫秒数,如果超出了,说明设置的超时时间过长if( diff >= (unsigned long long)apTimeout->iItemSize ){diff = apTimeout->iItemSize - 1;co_log_err("CO_ERR: AddTimeout line %d diff %d",__LINE__,diff);//return __LINE__;}// 将apItem加到末尾AddTail( apTimeout->pItems + ( apTimeout->llStartIdx + diff ) % apTimeout->iItemSize , apItem );return 0;
}
其实,这里有个概念,stTimeoutItemLink_t 与stTimeoutItem_t,也就是说,stTimeout_t里面管理的时60*1000个链表,而每个链表有一个或者多个stTimeoutItem_t,下面这个函数,就是把节点Item加入到链表的方法。
template <class TNode,class TLink>
void inline AddTail(TLink*apLink, TNode *ap)
{if( ap->pLink ){return ;}if(apLink->tail){apLink->tail->pNext = (TNode*)ap;ap->pNext = NULL;ap->pPrev = apLink->tail;apLink->tail = ap;}else{apLink->head = apLink->tail = ap;ap->pNext = ap->pPrev = NULL;}ap->pLink = apLink;
}
到这里,基本把一个超时事件添加到时间轮中了,这时就应该切换协程了co_yield_env
int ret = AddTimeout( ctx->pTimeout, &arg, now );int iRaiseCnt = 0;if( ret != 0 ){co_log_err("CO_ERR: AddTimeout ret %d now %lld timeout %d arg.ullExpireTime %lld",ret,now,timeout,arg.ullExpireTime);errno = EINVAL;iRaiseCnt = -1;}else{co_yield_env( co_get_curr_thread_env() );iRaiseCnt = arg.iRaiseCnt;}
接下来,看怎么检测超时事件co_eventloop
for(;;){// 等待事件或超时1msint ret = co_epoll_wait( ctx->iEpollFd,result,stCoEpoll_t::_EPOLL_SIZE, 1 );// 遍历所有ret事件处理for(int i=0;i<ret;i++){pfnPrepare(xxx)}// 取出所有的超时时间item,设置为超时TakeAllTimeout( ctx->pTimeout, now, plsTimeout );stTimeoutItem_t *lp = plsTimeout->head;while( lp ){lp->bTimeout = true;lp = lp->pNext;}// 将超时链表plsTimeout加入到plsActiveJoin<stTimeoutItem_t, stTimeoutItemLink_t>( plsActive, plsTimeout );lp = plsActive->head;while( lp ){// 弹出链表头,处理超时事件PopHead<stTimeoutItem_t,stTimeoutItemLink_t>( plsActive );if (lp->bTimeout && now < lp->ullExpireTime) {int ret = AddTimeout(ctx->pTimeout, lp, now);if (!ret) {lp->bTimeout = false;lp = plsActive->head;continue;}}// 只有stPool_t 才需要切协程,要切回去了if( lp->pfnProcess ){lp->pfnProcess( lp );}lp = plsActive->head;}// 如果传入该函数指针,则可以控制event_loop 退出if( pfn ){if( -1 == pfn( arg ) ){break;}}}
其中包括了定时事件处理,协程切换,主协程退出等操作。如果设置了主协程退出函数,则主协程可以正常的退出。
相关文章:

基于libco的c++协程实现(时间轮定时器)
在后端的开发中,定时器有很广泛的应用。 比如: 心跳检测 倒计时 游戏开发的技能冷却 redis的键值的有效期等等,都会使用到定时器。 定时器的实现数据结构选择 红黑树 对于增删查,时间复杂度为O(logn),对于红黑…...
java多线程与线程池-04线程池与AQS
第7章 线程池与AQS java.util.concurrent包中的绝大多数同步工具,如锁(locks)和屏障(barriers)等,都基于AbstractQueuedSynchronizer(简称AQS)构建而成。这个框架提供了一套同步管理的通用机制,如同步状态的原子性管理、线程阻塞与解除阻塞,还有线程排队等。 在JD…...
优化模型验证关键代码25:样本均值近似技术处理两阶段随机旅行商问题及Gurobipy代码验证
大多数数学规划模型都会考虑到研究问题中存在的不确定性,针对这些不确定性,两种常用的处理方法是鲁棒优化和随机规划。这篇论文我们关注后者,也就是两阶段随机旅行商问题;利用套期保值算法计算不同规模TSP的可行解,同时比较了样本均值近似技术的解的情况,并计算了该问题的…...

老爸:“你做的什么游戏测试简直是不务正业!”——我上去就是一顿猛如虎的解释。
经常有人问我:游戏测试到底是干什么呢?是游戏代练?每天玩游戏?装备随便造,怪物随便秒,线上GM指令随便用?可以每天玩玩游戏,不用忙工作,太爽了?有时朋友不理解…...
JVM垃圾回收调优知识点整理
目录 1、JVM内存模型 1.2、堆及垃圾回收 1.3、JVM参数设置经验: 1.4、对象逃逸分析:...

linux安装mysql-8.0.31
1)、下载mysql-8.0.31压缩包两种方式 a.本地下载后上传服务器解压,下载地址:https://downloads.mysql.com/archives/community/ b.服务器使用命令下载,注意:路径在那,就下载到那个位置。 wget https://dev.mysql.com/…...

2023 年会是网络安全的关键年吗?
过去 12 个月对网络安全领域和周围的每个人来说再次充满挑战。和往年不同,感觉很不一样,攻击源源不断。过去,大型漏洞每季度发生一次,但在过去一年中,在某些情况下,我们几乎每周都会处理严重漏洞。 已知利…...

【深度强化学习】(1) DQN 模型解析,附Pytorch完整代码
大家好,今天和各位讲解一下深度强化学习中的基础模型 DQN,配合 OpenAI 的 gym 环境,训练模型完成一个小游戏,完整代码可以从我的 GitHub 中获得: https://github.com/LiSir-HIT/Reinforcement-Learning/tree/main/Mod…...

Nginx服务优化与防盗链
目录 1.隐藏nginx版本号 1.查看版本号 2.隐藏版本信息 2.修改用户与组 3.缓存时间 4.日志分割 5.连接超时 6.更改进程数 7.网页压缩 8.配置防盗链 1.配置web源主机(192.168.156.10 www.lhf.com) 2.配置域名映射关系 3.配置盗链主机 ࿰…...
npm与yarn常用命令
npm npm -v:查看 npm 版本npm init:初始化后会出现一个 Package.json 配置文件,可以在后面加上 -y,快速跳到问答界面npm install:会根据项目中的 package.json 文件自动给下载项目中所需的全部依赖npm insall 包含 --…...

【C++】C++11新特性——右值引用
文章目录一、左值引用、 右值引用1.1 左值与右值1.2 左值引用1.3 右值引用二、右值引用的意义三、移动语句3.1 移动构造3.2 移动赋值3.3 总结四、move问题五、完美转发5.1 万能引用与折叠5.2 完美转发std::forward一、左值引用、 右值引用 1.1 左值与右值 我们经常能听到左值…...
C#基础教程21 正则表达式
文章目录 简介正则表达式语法字符集元字符转义字符量词贪婪匹配和非贪婪匹配正则表达式类Regex类Match方法Matches方法简介 正则表达式是一种描述字符串模式的语言,它可以用来匹配、查找、替换字符串中的模式。在C#中,我们可以使用System.Text.RegularExpressions命名空间下的…...

聚观早报|谷歌发布最大视觉语言模型;王兴投资王慧文ChatGPT项目
今日要闻:谷歌发布全球最大视觉语言模型;马斯克预计Twitter下季度现金流转正;王兴投资王慧文ChatGPT项目;美国拟明年 11 月开展载人绕月飞行;慧与科技宣布收购Athonet谷歌发布全球最大视觉语言模型 近日,来…...

java Spring5 xml配置文件方式实现声明式事务
在java Spring5通过声明式事务(注解方式)完成一个简单的事务操作中 我们通过注解方式完成了一个事务操作 那么 下面 我还是讲一下 基于xml实现声明式事务的操作 其实在开发过程中 大家肯定都喜欢用注解 因为他方便 这篇文章中的xml方式 大家做个了解就好 还是 我们的这张表 记…...

常用存储芯片-笔记本上固态硬盘PTS11系列推荐
在存储领域中,除了存储颗粒之外,还有一种极其重要的芯片:存储控制芯片。存储控制芯片是CPU与存储器之间数据交换的中介,决定了存储器最大容量、存取速度等多个重要参数。特别是在AI、5G、自动驾驶时代,对于数据处理及存…...

【AI绘图学习笔记】奇异值分解(SVD)、主成分分析(PCA)
这节的内容需要一些线性代数基础知识,如果你没听懂本文在讲什么,强烈建议你学习【官方双语/合集】线性代数的本质 - 系列合集 文章目录奇异值分解线性变换特征值和特征向量的几何意义什么是奇异值分解?公式推导SVD推广到任意大小矩阵如何求SV…...
【设计模式】模板方法模式和门面模式
模板方法模式和门面模式模板方法模式代码示例门面模式代码示例门面模式的应用场景模板方法模式 模板方法模式非常简单,就是定义了一个固定的公共流程,整个流程有哪些步骤是事先定义好的,具体的步骤则交由子类去实现。属于行为型设计模式。 简…...
Kubernetes未来十年的四大发展趋势
作者:李翔 跟大家已经感受到的一样,Kubernetes已经成为了云计算领域最具统治力的平台,成为了云原生开发的绝对标准,而伴随Kubernetes诞生的CNCF (Cloud Native Computing Foundation) 也因此成为了业界影响力巨大的组织。在成为云…...
一、sql 基础知识、函数和子查询
MySQL 是一种流行的关系型数据库管理系统,使用 SQL 语言进行数据管理和操作。在 MySQL 中,常用的语句包括 SELECT 查询语句、WHERE 条件语句、算术表达式、函数、聚合函数、自定义函数、逻辑表达式、子查询和连接。这些语句可以帮助用户快速地进行数据查…...
产品射频认证笔记
文章目录1. 射频监管认证的目的:1.1 确保 RF 产品在其预期环境中按预期运行1.2 确保射频产品不会干扰其他电子或射频设备2. 射频认证地区规范3. FCC简介4. FCC认证需要准备的内容:5. 射频监管测量会话期间测量以下射频属性:6. 调整射频参数6.…...

华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...
Android Wi-Fi 连接失败日志分析
1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分: 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析: CTR…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...

微信小程序云开发平台MySQL的连接方式
注:微信小程序云开发平台指的是腾讯云开发 先给结论:微信小程序云开发平台的MySQL,无法通过获取数据库连接信息的方式进行连接,连接只能通过云开发的SDK连接,具体要参考官方文档: 为什么? 因为…...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...
Java 二维码
Java 二维码 **技术:**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...

STM32HAL库USART源代码解析及应用
STM32HAL库USART源代码解析 前言STM32CubeIDE配置串口USART和UART的选择使用模式参数设置GPIO配置DMA配置中断配置硬件流控制使能生成代码解析和使用方法串口初始化__UART_HandleTypeDef结构体浅析HAL库代码实际使用方法使用轮询方式发送使用轮询方式接收使用中断方式发送使用中…...

在 Spring Boot 中使用 JSP
jsp? 好多年没用了。重新整一下 还费了点时间,记录一下。 项目结构: pom: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://ww…...
在树莓派上添加音频输入设备的几种方法
在树莓派上添加音频输入设备可以通过以下步骤完成,具体方法取决于设备类型(如USB麦克风、3.5mm接口麦克风或HDMI音频输入)。以下是详细指南: 1. 连接音频输入设备 USB麦克风/声卡:直接插入树莓派的USB接口。3.5mm麦克…...