基于libco的c++协程实现(时间轮定时器)

news2024/11/29 6:26:59

在后端的开发中,定时器有很广泛的应用。

比如:

心跳检测

倒计时

游戏开发的技能冷却

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 };	// 40s
	stTimeoutItem_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_t
	struct 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,赋值为1
	int 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*1000
	unsigned 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(;;)
	{
		// 等待事件或超时1ms
		int 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加入到plsActive
		Join<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;
			}
		}
	}

其中包括了定时事件处理,协程切换,主协程退出等操作。如果设置了主协程退出函数,则主协程可以正常的退出。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/402003.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Unity趣味编程】——c++实现小球的自由移动

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…

IOS APP Store发布流程

一、官网添加新版本 进入官网,点击左上角号&#xff0c;添加新版本 新增版本 -> 填写推广文本、更新内容 -> 点击存储 二、Xcode打包 苹果打包需要注意版本号、版本code不能与线上重复。 点击Xcode -> Product -> Archive&#xff0c;选择App Store 第二步选择导出…

移除元素问题解决方法------LeetCode-OJ题

问题&#xff1a; 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 要求&#xff1a; 不要使用额外的数组空间&#xff0c;你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 元素的顺序可以改…

爆品分析第5期 | 一条视频带货3700+,这款斋月不锈钢厨具套装火了!

俗话说民以食为天&#xff0c;吃在任何一种文化中都占据重要的位置&#xff0c;要做出一道美味佳肴&#xff0c;除了食材、烹饪者的自身厨艺之外&#xff0c;还少不了一口好锅。新冠疫情以来&#xff0c;全世界范围内的封闭让很多人养成了居家做饭的习惯&#xff0c;不仅为厨具…

Spark高手之路1—Spark简介

文章目录Spark 概述1. Spark 是什么2. Spark与Hadoop比较2.1 从时间节点上来看2.2 从功能上来看3. Spark Or Hadoop4. Spark4.1 速度快4.2 易用4.3 通用4.4 兼容5. Spark 核心模块5.1 Spark-Core 和 弹性分布式数据集(RDDs)5.2 Spark SQL5.3 Spark Streaming5.4 Spark MLlib5.5…

玩转金山文档 3分钟让你的文档智能化

在上个月底&#xff0c;我们给大家推荐了金山轻维表的几个使用场景&#xff0c;社群中不少用户反响很好&#xff0c;对其中一些场景的解决方案十分感兴趣。但也有一些人表示&#xff0c;有些场景不知道如何实现&#xff0c;希望我们能提供模版/教程。这次我们将做一期热门模板盘…

案例20-内存长期占用导致系统变慢

一、背景介绍 本篇博客是对生产环境出现内存长期占用导致系统变慢的原因分析及总结。 现状&#xff1a; 系统出现了爬取加载慢的情况&#xff0c;核心服务的内存占用很高的情况。 如下图&#xff1a; 二、思路&方案 查询服务内存占用过高的原因&#xff1a; 1、服务启动…

再学C语言43:字符串表示和字符串I/O

字符串是以空字符&#xff08;\0&#xff09;结尾的char数组 在程序中定义字符串 1&#xff09;字符串常量 字符串常量&#xff08;字符串文字&#xff09;&#xff1a;位于一对双引号中的任何字符 双引号里的字符加上编译器自动提供的结束标志\0字符&#xff0c;作为一个字…

线程安全 List 效率测试

List 常见类以及各自优缺点可自行参考 https://blog.csdn.net/weixin_39883065/article/details/111197724 本机环境 java 版本&#xff1a;1.8.0_161 window 信息&#xff1a; 测试代码 下面通过代码测试 List 线程安全类 Vector、Collections.synchronizedList(List lis…

在空投之后,Blur能否颠覆OpenSea的主导地位?

Mar. 2023, Daniel数据源&#xff1a; NFT Aggregators Overview & Aggregator Statistics Overview & Blur Airdrop一年前&#xff0c;通过聚合器进行的NFT交易量开始像滚雪球一样增长&#xff0c;有时甚至超过了直接通过市场平台的交易量。虽然聚合器的使用量从10月到…

【Redis】P1 Redis - NoSQL

Redis - NoSQLSQL 与 NoSQL差别一&#xff1a;结构化 与 非结构化差别二&#xff1a;关联性 与 非关联性差别三&#xff1a;规范化查询语句 与 非规范化差别四&#xff1a;事务 与 无事务差别五&#xff1a;磁盘存储 与 内存存储RedisRedis 的安装当前数据库存储主要分为 关系型…

vivo全球商城:库存系统架构设计与实践

作者&#xff1a;vivo官网商城开发团队 - Xu Yi、Yan Chao 本文是vivo商城系列文章&#xff0c;主要介绍vivo商城库存系统发展历程、架构设计思路以及应对业务场景的实践。 一、业务背景 库存系统是电商商品管理的核心系统&#xff0c;本文主要介绍vivo商城库存中心发展历程、…

Golang的下载与安装

Windows系统 进入golang官方下载网站:所有版本 - Go 编程语言如图所示 下载后打开您下载的 MSI 文件,然后按照提示安装 Go。 验证是否已安装 Go。

【百宝书Linux】WSL-Windows中的Linux安装教程

大家好&#xff0c;我是涵子。今天我们来讲讲WSL。 配置环境&#xff1a; 一台装有Windows10及以上的电脑 较高性能的CPU 网络 目录 一、安装Ubuntu 二、安装WSL 三、运行WSL 四、补充 一、安装Ubuntu 首先&#xff0c;我们打开Microsoft Store。 搜索Ubuntu后&#xff0c…

容器方式搭建免费的表白网站--黑屏红心雪花飘零--背景音乐《三生三世》(2023.310更新)

效果图 拉取镜像 docker pull swr.cn-north-1.myhuaweicloud.com/loves/aixinbiaobai:20230310运行容器 docker run -di --name aixinbiaobai -e GIRLNAME=李华 -e BOYNAME=张三 -e STARTTIME=2023,2,10 -p 80:80 swr.cn-north-1.myhuaweicloud.com/loves/aixinbiaobai:2023…

【9】基础语法篇 - VL9 使用子模块实现三输入数的大小比较

VL9 使用子模块实现三输入数的大小比较 【报错】官方平台得背锅 官方平台是真的会搞事情,总是出一些平台上的莫名其妙的错误。 当然如果官方平台是故意考察我们的细心程度,那就当我没有说!! 在这个程序里,仿真时一直在报错 错误:无法在“test”中绑定wire/reg/memory“t…

0405习题总结-不定积分

文章目录1 不定积分的基本概念2 直接积分法-基本积分公式3 第一换元法-凑微分形式法4 第二类换元法5 分部积分求不定积分6 表格法积分7 有理函数求积分后记1 不定积分的基本概念 例1 f(x){x1,x≥012e−x12,x<0求∫f(x)dxf(x) \begin{cases} x1,\quad x\ge0\\ \frac{1}{2}e^…

【C++】register 关键字

文章目录一. 什么是寄存器&#xff1f;二. 为什么要存在寄存器&#xff1f;三. register 修饰变量一. 什么是寄存器&#xff1f; 我们都知道&#xff0c;CPU主要是负责进行计算的硬件单&#xff0c;但是为了方便运算&#xff0c;一般第一步需要先把数据从内存读取到CPU内&…

php设计模式-组合模式的运用

介绍 PHP的组合模式是一种设计模式&#xff0c;用于将对象组合成树形结构以表示“部分-整体”的层次结构。该模式允许客户端统一处理单个对象和组合对象&#xff0c;使得客户端在处理对象时不需要知道对象是否为单个对象还是组合对象。 在组合模式中&#xff0c;有两种类型的…

【零基础入门学习Python---Python的基本语法使用】

一.Python基本语法使用 Python是一种易学且功能强大的编程语言,具有简洁的语法和广泛的应用领域。在本文中,我们将介绍Python的基本语法使用,以帮助初学者快速入门Python编程。 1.1 注释 Python 支持两种类型的注释:单行注释和多行注释。 单行注释:以 # 符号开头,从 # …