一种小型项目的超时机制方案

news2024/12/28 19:49:47
设计背景
  • 在小型项目中,状态机的跳转往往依赖于某个事件的状态,因此监控某个事件是否处于超时状态就至关重要;
注意事项
  • 超时机制应该能够准确的判断出事件是否真正超时,并预留出设置自定义超时处理的接口;
  • 超时机制不应该占用过多的系统资源;
  • 超时机制应该作为一个独立组件,与各层级(驱动层/协议层/应用层等)之间解耦,只预留出简洁的操作接口;
相关设计思路
  • 根据前文描述,创建两个task;monitor_task用于轮询是否有event超时,handle_task用于执行event的超时处理;
  • 对于monitor_task轮询event是否超时的操作,借助时间轮算法思想,以最大超时时间max_timeout作为参考设定时间轮周期,根据实际情况划分周期的刻度;定义一个时间指针,该指针每一次跳动一个刻度,周而复始;时钟指针指向的刻度即为超时刻度,将该刻度下的event事件交由handle_task去做处理;比如:
    1. max_timeout为3000 ms,则设定时间轮周期为4000 ms;设定8个刻度,则刻度间时间差为500 ms;于是monitor_task的运行周期为500 ms,handle_task的运行周期为<=500ms;
    2. 因为设定为8个刻度,因此需要声明8个数组,该数组用于保存具体的event;当时间指针指向某个刻度,则表示该刻度下的数组中的event已经超时;event相关定义如下:
    /*描述一个event*/
    typedef struct {
    	int8_t   flag;		//event标志位——表明是否有效
    	uint32_t  id;		//id
    	int32_t (*callback)(void* arg);		//超时执行函数
    } TIMEOUT_EVENT_UNIT_t;
    
    /*描述一个event数组*/
    typedef struct {
    	uint8_t count;			//数组的元素数量
    	TIMEOUT_EVENT_UNIT_t unit[EVENT_NUM];
    } TIMEOUT_EVENT_t;
    
  • monitor_task中的监控处理和handle_task中的超时处理如下:
    static void *monitor_task(void *arg)
    {
    	while (1) {
        	pthread_mutex_lock(&g_thread_mutex);
        	g_timeout_sec ++;
        	if (g_timeout_sec == TIMEOUT_SEC_NUM) { /*周而复始*/
            	g_timeout_sec = TIMEOUT_SEC_0_5;
        	}
        	p_global_scale   = &g_event[g_timeout_sec];	//时间指针指向下一刻度,该刻度下event超时
        	p_global_timeout = p_global_scale;          //超时指针指向该刻度
        	pthread_mutex_unlock(&g_thread_mutex);
        	usleep(MOITOR_TASK_CYCLE_MS*MS_TO_US);
    	}
    	
    	pthread_exit(NULL);
    	return NULL;
    }
    
    static void *handle_task(void *arg)
    {
    	int32_t i = 0;
    
    	while (1) {
        	pthread_mutex_lock(&g_thread_mutex);
        	for (i = 0; i < EVENT_NUM; i ++) {
            	if (p_global_timeout->count == 0) {
                	break;
            	}
            	if (p_global_timeout->unit[i].flag && 
                	p_global_timeout->unit[i].callback != NULL) {
                	p_global_timeout->unit[i].callback(&p_global_timeout->unit[i].id);
                	p_global_timeout->unit[i].flag = 0;
                	p_global_timeout->count --;
            	}
        	}
        	pthread_mutex_unlock(&g_thread_mutex);
        	usleep(HANDLE_TASK_CYCLE_MS*MS_TO_US);
    	}
    
    	pthread_exit(NULL);
    	return NULL;
    }
    
  • 对于event的插入和删除操作,有以下两种方案:
    1. event插入时找到对应的数组,在数组中找到空闲位置插入;删除时,找到对应的event将其删除;两者都需要进行遍历操作;
    int32_t unit_timeout_event_add(uint32_t id, TIMEOUT_SEC_e time, int32_t (*callback)(void* arg))
    {
    	/* ...... */
    	p_event = g_event[num];
    	for (i = 0; i < EVENT_NUM; i ++) {
        	if (p_event[i].flag == 0) {
            	p_event[i].flag = 1;
            	p_event[i].id = id;
            	p_event[i].callback = callback;
            	goto __ADD_SUCCESS;
        	}
    	}
    	/* ...... */
    	printf("%s err!\n", __func__);
    	return -1;
    __ADD_SUCCESS:
    	return 0;
    }
    
    int32_t unit_timeout_event_delete(uint32_t id)
    {
    	int i, j;
    	/* ...... */
    	for (i = 0; i < SCALE_NUM; i++) {
        	for (j = 0; j < EVENT_NUM; j ++) {
            	if (g_event[i][j].flag && g_event[i][j].id == id) {
                	g_event[i][j].flag = 0;
            	}
        	}
    	}
    	/* ...... */
    	return 0;
    }
    
    1. 将event重新映射,在找到对应的数组后,通过重映射id直接插入即可;删除时,通过重映射id直接删除即可;
    typedef enum {
    	TIMEOUT_ID_1 = 0,
    	TIMEOUT_ID_2,
    	/* ...... */
    	TIMEOUT_ID_NUM,
    } TIMEOUT_ID_e;
    
    int32_t unit_timeout_event_add(TIMEOUT_ID_e id, TIMEOUT_SEC_e time, int32_t (*callback)(void* arg))
    {
    	/* ...... */
    	p_event->unit[id].flag = 1;
    	p_event->unit[id].id   = id;
    	p_event->unit[id].callback = callback;
    	p_event->count ++;
    	/* ...... */
    	return 0;
    }
    
    int32_t unit_timeout_event_delete(TIMEOUT_ID_e id)
    {
    	/* ...... */
    	for (i = 0; i < SCALE_NUM; i ++) { 
        	if (g_event[i].unit[id].flag == 1) {
            	g_event[i].unit[id].flag = 0;
            	g_event[i].count --;
        	}
        }
    	/* ...... */
    	return 0;
    }
    
    1. 总的来说,方案2相较于方案1采用了空间换时间的操作,即通过重新映射event id,减少了遍历操作即减少了系统资源的占用,但同样减少了可监控的event数量;
疑惑
  • 为什么max_timeout为3000 ms,刻度间时间差为500 ms,却设置时间轮周期为4000 ms(比max_timeout多了两个刻度周期)?是因为存在以下两种情况需要规避:
  • monitor_task阻塞499ms,即将获取到调度权,假设此时时间指针指向刻度0;恰巧此时插入了一个超时event,超时时间设置为500 ms;所以这个时候这个event应该插入到刻度2而非刻度1,以确保不会被误判;但此时存在event实际上在500ms超时了,但却在500~1000ms才感知到的问题,对于这个问题只能根据实际情况,通过增加刻度来减小误差;
  • 在上述情况下,假设设置时间轮周期为3000 ms(6个刻度),同时假设此时时间指针指向刻度0;恰巧此时插入了一个超时event,超时时间设置为3000 ms,此时event插入至刻度0;随后handle_task立马执行,所以event被误判为超时;
    在这里插入图片描述
流程图

在这里插入图片描述

参考链接
  • 任务调度:时间轮算法经典案例解析及应用实现
  • RPC实现原理之核心技术-时间轮
代码链接
  • gitee or github
本文提出了一种小型项目的超时机制方案,不当之处请在评论区指出

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

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

相关文章

什么是大数?大数有什么用?如何使用大数?

0 什么是大数 如果基本的整数和浮点数精度不能够满足需求&#xff0c;那么可以使用 javamath 包中两个很有用的类:BigInteger和 BiDecimal。 这两个类可以处理包含任意长度数字序列的数值。 BigInteger类实现任意精度的整数运算&#xff0c;BigDecimal实现任意精度的浮点数运算…

Android | Fragment

Android Fragment Fragment 概念 Fragment 表示应用界面中可重复的一部分&#xff0c;Fragment 定义和管理自己的布局&#xff0c;具有自己的生命周期&#xff0c;并且可以处理自己的输入事件。fragment 不能独立存在&#xff0c;而是必须由 Activity 或另一个 fragment 托管…

麦克纳姆轮(麦轮)原理

一、麦轮原理 麦克纳姆轮&#xff1a;简称麦轮&#xff0c;由轮毂和围绕轮毂的辊子组成。 辊子&#xff1a;没有动力的从动小滚轮。 麦克纳姆轮辊子轴线和轮毂轴线夹角是45度。 A轮&#xff08;左旋&#xff09;与B轮&#xff08;右旋&#xff09;互为镜像关系 麦轮在车上的…

Go语言开发小技巧易错点100例(五)

往期回顾&#xff1a; Go语言开发小技巧&易错点100例&#xff08;一&#xff09;Go语言开发小技巧&易错点100例&#xff08;二&#xff09;Go语言开发小技巧&易错点100例&#xff08;三&#xff09;Go语言开发小技巧&易错点100例&#xff08;四&#xff09; …

打工人必学的法律知识(五)——《女职工劳动保护特别规定》

女职工劳动保护特别规定https://flk.npc.gov.cn/detail2.html?ZmY4MDgwODE2ZjNjYmIzYzAxNmY0MTBmMWVkNTE0NTE &#xff08;2012年4月18日国务院第200次常务会议通过 2012年4月28日中华人民共和国国务院令第619号公布 自公布之日起施行&#xff09;第一条 为了减少和解决女职工…

如何减少频繁创建数据库连接的性能损耗?

为极速开发出一套某垂直领域电商系统&#xff0c;采用最简架构&#xff1a; 前端一台Web服务器运行业务代码后端一台DB服务器存储业务数据 大多系统初生时就是这样&#xff0c;只是随业务不但发展变得复杂&#xff0c;架构迭代。系统上线后&#xff0c;虽用户量不大&#xf…

第十三届蓝桥杯大赛软件类决赛Java大学B组C题——左移右移

【问题描述】 小蓝有一个长度为 N 的数组&#xff0c;初始时从左到右依次是 1, 2, 3, . . . N。 之后小蓝对这个数组进行了 M 次操作&#xff0c;每次操作可能是以下 2 种之一&#xff1a; 左移 x&#xff0c;即把 x 移动到最左边。右移 x&#xff0c;即把 x 移动到最右边。 …

数字信号处理音频FIR去噪滤波器(基于MATLAB GUI的开发完整代码+报告+课设)

1、内容简介利用MATLAB GUI设计平台&#xff0c;用窗函数法设计FIR数字滤波器&#xff0c;对所给出的含有噪声的声音信号进行数字滤波处理&#xff0c;得到降噪的声音信号&#xff0c;进行时域频域分析&#xff0c;同时分析不同窗函数的效果。将文件解压至一个目录下&#xff0…

ithewei的2022年度总结

窗外的雨&#xff0c;顺着晒衣架汇聚成豆大的珠&#xff0c;落到一楼庭院顶棚上&#xff0c;嘀嗒不停&#xff1b; 路上的车&#xff0c;由远及近&#xff0c;又由近及远&#xff0c;疾驰而过&#xff1b; 床边的猫&#xff0c;也已入睡&#xff0c;时不时发出一两声细微的鼾声…

面试问:@Resource 和@Autowired 的区别是什么?该怎么回答

Resource 和 Autowired 这两个注解的作用都是Spring生态里面去实现 Bean 的依赖注入 Autowired注解的作用 Autowired是Spring里面提供的一个注解&#xff0c;它默认是根据类型来实现Bean的依赖注入。 Autowired注解里面有一个required属性&#xff0c;它的默认值是true&#…

人大金仓数据库安装篇

图形化安装 规划安装路径与修改目录属主 先用root账户挂载金仓镜像包 mount -o loop /install/KingbaseES_V008R006C005B0023_Lin64_single_install.iso /mnt 切换kingbase用户来安装金仓数据库 进入挂载目录/mnt执行./setup.sh 将金仓注册为系统服务 /KingbaseES/V8/Scrip…

1592_AURIX_TC275_PMU_部分安全措施

全部学习汇总&#xff1a; GreyZhang/g_TC275: happy hacking for TC275! (github.com) UCB的几种状态通过寄存器的数值可以直接获取到。启动代码会识别到UCB的错误&#xff0c;如果识别到后悔停止继续&#xff0c;因此不会启动MCU。启动代码也会检查HSM是否存在以及是否需要启…

基于TensorFlow2搭建神经网络实现鸢尾花iris.txt分类

分三步&#xff1a;本地读取鸢尾花数据集搭建神经网络优化本地读取鸢尾花数据集读取本地数据集的两种方法读取本地数据集有两种方法&#xff1a;&#xff08;1&#xff09;利用pandas中的函数读取&#xff0c;并处理成神经网络需要的数据结构pd.read_csv(文件名, header第几行作…

使用码云Gitee创建代码仓库并提交代码

目录 1. 登录Gitee官网 2. 创建代码仓库 3. 克隆仓库到本地 4. 提交代码到Gitee仓库 官方文档&#xff1a;创建你的第一个仓库 - Gitee.com 1. 登录Gitee官网 官网地址&#xff1a;Gitee - 基于 Git 的代码托管和研发协作平台 2. 创建代码仓库 点击图中加号创建仓库&am…

String的不可变性

1.什么是不可变对象 如果一个对象在创建之后就不能再改变它的状态&#xff0c;那么这个对象是不可变的&#xff08;Immutable&#xff09;。不能改变状态的意思是&#xff0c;不能改变对象内的成员变量&#xff0c;包括基本数据类型变量的值不能改变&#xff0c;引用类型的变量…

数据库面试题

数据库基础知识 什么是MySQL&#xff1f; MySQL是一个数据库管理系统。 数据库是数据的结构化集合。 MySQL数据库是关系型的。 关系数据库将数据存储在单独的表中&#xff0c;而不是将所有数据放在一个大仓库中。数据库结构被组织成针对速度进行了优化的物理文件。具有对象&a…

第五届字节跳动青训营 前端进阶学习笔记(一)前端和HTML

文章目录1.前言2.什么是前端3.前端需要关注哪些问题4.HTML语法5.HTML标签6.HTML中的内容划分7.总结这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天 1.前言 本次课程主要介绍了&#xff1a; 前端要解决的基本问题HTML的基本定义和语法为什么要语义化和怎么做的语义化 …

深度学习——梯度

一、初识梯度 import torch x torch.arange(4.0) print(x) x.requires_grad_(True) y 2 * torch.dot(x, x) #torch.matmul(x, x) print(y) y.backward() print(x.grad) print(x.grad 4*x)输出&#xff1a; tensor([0., 1., 2., 3.]) tensor(28., grad_fn<MulBackward0&g…

批次标准化Batch Normalization

批次标准化Batch Normalization 目录 批次标准化Batch Normalization 为什么需要批次标准化 产生上述变化趋势不匹配的原因 处理方法 处理方法的优化 Batch Normalization的引出Testing时的相应处理 批次标准化Batch Normalization 第五节 2021 - 类神经网络训练不起来怎么…

从【卡内基梅隆大学机器人概论课】认识机器人学科需要哪些技能栈

闲来无事&#xff0c;找到了卡内基梅隆大学机器人研究所&#xff0c;看了下他们机器人的教育&#xff0c;不得不感慨相比我们学校先进了不知多少&#xff0c;是真真切切让同学们去了解机器人的方方面面&#xff0c;下面摘自它们的概论作业。 作业0 作业1 作业2 作业3 作业4 作…