200行代码解读TDEngine背后的定时器
作者 | beyondma来源 | CSDN博客
导读:最近几周,本文作者几篇有关陶建辉老师最新的创业项目-TdEngine代码解读文章出人意料地引起了巨大的反响,原以为C语言已经是昨日黄花,不过从读者的留言来看,C语言还是老当益壮,依旧有着巨大的影响力,作者就以此为契机不断向陶老师请教,这次再给大家带来TdEngine计时器的代码解读。
其主要源码地址如下:https://github.com/taosdata/TDengine/blob/master/src/util/src/ttimer.c
TDEngine为何要自己实现Timer
其实一开始在读到这段代码时笔者也有类似的疑问,因为操作系统的内核基本都实现了定时器的功能,可以直接调用,但是深入思考一下就会发现由于TdEginge本身是个时序数据库的应用,而由于数据库的特殊性,其对库底层的需求其实与操作系统的内核需求类似,我们知道直接调用操作系统的timer需要在到时后启动一个对应的线程去处理对应的中断请求,而这对于TdEginge这种动辙需要上万个定时器的数据库应用来说无疑是一笔巨大的开销,这显然不是陶老师这种极端要求效率的程序员能够接受的。
所以TDEngine的定时器的基本思路是基于操作系统的timer,来封装自身的定时器功能,使所有的timer控制器运行在一个线程池,而在同一timer控制器下的timer则运行在同一线程内以此来达到节约资源的目的。
TDEngine Timer的基本工作原理
简单来说TDEngine Timer的工作流程如下:
一.Timer初始化
主要完成以下工作:
初始化Timer线程池
启动操作系统的Timer,并注册处理timer循环处理函数taosTimerLoopFunc。
二.Timer启动
计算当前需要启动timer的调起序列号(index),即taosTimerLoopFunc运行在第几个Loop时会调起当前的timer的处理函数。
对于调起序列号相同的timer加入双链表tmrList[当前index],其在链表中的位置依据其到期时间的先后排序,注:(由于之前启动的操作系统timer也是有循环周期的,所以TDEngine timer也可能不是要在当前周期内调起。所以调起序列号相同,但是调起周期运可能有前后次序,其在链表中的位置其实是要买调起周期的先后排序的。)
工作过程图示:由于作者美工基础较差,所以虽然花了很久但是下图的效果其实一般,请大家能够直观感受即可。
简要地讲,taosTimerLoopFunc函数循环处理tmrList,并将当前启动序列号对应的tmrList交由taosTmrProcessList处理,taosTmrProcessList调用处在当前启动序列号(index)且处在当前循环序列号(cycle)的timer的回调(handler)函数。
结合代码的解读
初始化函数的解读,具体代码及注释如下:
void *taosTmrInit(int maxNumOfTmrs, int resolution, int longest, char *label) { static pthread_once_t tmrInit = PTHREAD_ONCE_INIT; tmr_ctrl_t * pCtrl; pthread_once(&tmrInit, taosTmrModuleInit);//pthread_once保证TmrModule只会被运行一次 int tmrCtrlId = taosAllocateId(tmrIdPool); if (tmrCtrlId < 0) { tmrError("%s bug!!! too many timers!!!", label); return NULL; } pCtrl = tmrCtrl + tmrCtrlId; tfree(pCtrl->tmrList); tmrMemPoolCleanUp(pCtrl->poolHandle); memset(pCtrl, 0, sizeof(tmr_ctrl_t)); pCtrl->tmrCtrlId = tmrCtrlId; strcpy(pCtrl->label, label); pCtrl->maxNumOfTmrs = maxNumOfTmrs; if ((pCtrl->poolHandle = tmrMemPoolInit(maxNumOfTmrs + 10, sizeof(tmr_obj_t))) == NULL) { tmrError("%s failed to allocate mem pool", label); tmrMemPoolCleanUp(pCtrl->poolHandle); return NULL; } if (resolution < MSECONDS_PER_TICK) resolution = MSECONDS_PER_TICK;//初始化分辨率 pCtrl->resolution = resolution; pCtrl->maxTicks = resolution / MSECONDS_PER_TICK;//初始化最大的tick pCtrl->ticks = rand() / pCtrl->maxTicks; pCtrl->numOfPeriods = longest / resolution;//初始化最大周期 if (pCtrl->numOfPeriods < 10) pCtrl->numOfPeriods = 10; pCtrl->tmrList = (tmr_list_t *)malloc(sizeof(tmr_list_t) * pCtrl->numOfPeriods);//初始化tmrList目前还都是NULL for (int i = 0; i < pCtrl->numOfPeriods; i++) { pCtrl->tmrList[i].head = NULL; pCtrl->tmrList[i].count = 0; } if (pthread_mutex_init(&pCtrl->mutex, NULL) < 0) { tmrError("%s failed to create the mutex, reason:%s", label, strerror(errno)); taosTmrCleanUp(pCtrl); return NULL; } pCtrl->signature = pCtrl; numOfTmrCtrl++; tmrTrace("%s timer ctrl is initialized, index:%d", label, tmrCtrlId); return pCtrl;//初始化完成
}
2. 模块初化函数:我们看到在初始化函数中调用了模块初始化函数进行线程池及操作系统定时器的启动处理,其具体代码及注释如下:
void taosTmrModuleInit(void) { tmrIdPool = taosInitIdPool(maxNumOfTmrCtrl);//初始化id池 memset(tmrCtrl, 0, sizeof(tmrCtrl)); #ifdef LINUX pthread_t thread; pthread_attr_t tattr; pthread_attr_init(&tattr); pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED); if (pthread_create(&thread, &tattr, taosProcessAlarmSignal, NULL) != 0) { tmrError("failed to create timer thread");//初始化操作系统timer return; } pthread_attr_destroy(&tattr);
#else taosInitTimer(taosTimerLoopFunc, MSECONDS_PER_TICK);
#endif tmrQhandle = taosInitScheduler(10000, taosTmrThreads, "tmr");//初始化handle的Scheduler tmrTrace("timer module is initialized, thread:%d", taosTmrThreads);
}
3.timer启动函数
tmr_h taosTmrStart(void (*fp)(void *, void *), int mseconds, void *param1, void *handle) { tmr_obj_t * pObj, *cNode, *pNode; tmr_list_t *pList; int index, period; tmr_ctrl_t *pCtrl = (tmr_ctrl_t *)handle; if (handle == NULL) return NULL; period = mseconds / pCtrl->resolution;//初始化period数 if (pthread_mutex_lock(&pCtrl->mutex) != 0) tmrError("%s mutex lock failed, reason:%s", pCtrl->label, strerror(errno)); pObj = (tmr_obj_t *)tmrMemPoolMalloc(pCtrl->poolHandle); if (pObj == NULL) { tmrError("%s reach max number of timers:%d", pCtrl->label, pCtrl->maxNumOfTmrs); pthread_mutex_unlock(&pCtrl->mutex); return NULL; } pObj->cycle = period / pCtrl->numOfPeriods;//初始化周期数,即使用period除以每个循环中共有几个period,得到需要在第几个循环中调起timer pObj->param1 = param1; pObj->fp = fp; pObj->timerId = pObj; pObj->pCtrl = pCtrl; index = (period + pCtrl->periodsFromStart) % pCtrl->numOfPeriods;//初始化启动序列号 int cindex = (pCtrl->periodsFromStart) % pCtrl->numOfPeriods; pList = &(pCtrl->tmrList[index]); pObj->index = index; cNode = pList->head; pNode = NULL; while (cNode != NULL) { if (cNode->cycle < pObj->cycle) { pNode = cNode; cNode = cNode->next; } else { break; } } pObj->next = cNode; pObj->prev = pNode; if (cNode != NULL) { cNode->prev = pObj; } if (pNode != NULL) { pNode->next = pObj; } else { pList->head = pObj; } pList->count++; pCtrl->numOfTmrs++; //以上为将相同index的timer按照先后顺序在pList中排序,具体下面会有图例展示。 if (pthread_mutex_unlock(&pCtrl->mutex) != 0) tmrError("%s mutex unlock failed, reason:%s", pCtrl->label, strerror(errno)); tmrTrace("%s %p, timer started, fp:%p, tmr_h:%p, index:%d, total:%d cindex:%d", pCtrl->label, param1, fp, pObj, index, pCtrl->numOfTmrs, cindex); return (tmr_h)pObj;
}
可能各位读者也被以上代码中的pobj,cnode,pnode搞的晕头转向,下面我们以3个timer为例,假如他们的index和cycle都相同,那么他们分别调用完taosTmrStart之后tmrList会是什么情况,可以参考下表。
4.loopFunc和taosTmrProcessList
具体代码及注释如下:
void taosTmrProcessList(tmr_ctrl_t *pCtrl) { unsigned int index; tmr_list_t * pList; tmr_obj_t * pObj, *header; pthread_mutex_lock(&pCtrl->mutex); index = pCtrl->periodsFromStart % pCtrl->numOfPeriods;//计算当前index pList = &pCtrl->tmrList[index]; while (1) { header = pList->head; if (header == NULL) break; if (header->cycle > 0) {/*如当前index对应的tmrList[index]的cycle大于0则下面会将其cycle减1*/ pObj = header; while (pObj) { pObj->cycle--; pObj = pObj->next; } break; } pCtrl->numOfTmrs--; tmrTrace("%s %p, timer expired, fp:%p, tmr_h:%p, index:%d, total:%d", pCtrl->label, header->param1, header->fp, header, index, pCtrl->numOfTmrs); pList->head = header->next;/*如运行到此处则当前header已经expired,重新整理pList*/ if (header->next) header->next->prev = NULL; pList->count--; header->timerId = NULL; SSchedMsg schedMsg; schedMsg.fp = NULL; schedMsg.tfp = header->fp; schedMsg.ahandle = header->param1; schedMsg.thandle = header; taosScheduleTask(tmrQhandle, &schedMsg); tmrMemPoolFree(pCtrl->poolHandle, (char *)header); } pCtrl->periodsFromStart++; pthread_mutex_unlock(&pCtrl->mutex);
}
void *taosTimerLoopFunc(int signo) { tmr_ctrl_t *pCtrl; int count = 0; for (int i = 1; i < maxNumOfTmrCtrl; ++i) { pCtrl = tmrCtrl + i; if (pCtrl->signature) { count++; pCtrl->ticks++; if (pCtrl->ticks >= pCtrl->maxTicks) {/*如当前的ticks已经大于maxTicks则需要调起taosTmrProcessList对当前index的timer进行处理*/ taosTmrProcessList(pCtrl); pCtrl->ticks = 0; } if (count >= numOfTmrCtrl) break; } } return NULL;
}
最后我也想留一个开放性问题,也就是tmrList使用双链表实现的最大好处是什么,能否使用单链表实现?欢迎读者留言说出你的看法。
原文链接:
https://blog.csdn.net/BEYONDMA/article/details/98473143
(*本文为 AI科技大本营转载文章,转载请联系原作者)
社群福利
扫码添加小助手,回复:大会,加入2019 AI开发者大会福利群,每周一、三、五更新技术福利,还有不定期的抽奖活动~
◆
精彩推荐
◆
60+技术大咖与你相约 2019 AI ProCon!大会早鸟票已售罄,优惠票速抢进行中......2019 AI开发者大会将于9月6日-7日在北京举行,这一届AI开发者大会有哪些亮点?一线公司的大牛们都在关注什么?AI行业的风向是什么?2019 AI开发者大会,倾听大牛分享,聚焦技术实践,和万千开发者共成长。
推荐阅读
七夕大礼包:26个AI学习资源送给你!
玩王者荣耀用不好英雄?两阶段算法帮你精准推荐精彩视频
用Python给女友准备个绝对甜蜜的七夕礼物
突发!Python再次第一,Java和C下降,凭什么?
白话中台战略:中台是个什么鬼?
伟创力回应扣押华为物资;谷歌更新图片界面;Python 3.8.0b3 发布 | 极客头条
沃尔玛也要发币了,Libra忙活半天为他人做了嫁衣?
知名饮料制造商股价暴涨500%惊动FBI,只因在名字中加入了"区块链" ?
你点的每个“在看”,我都认真当成了喜欢
200行代码解读TDEngine背后的定时器相关推荐
- 200 行代码解读国产数据库阿里 OceanBase 的速度源头!| CSDN 博文精选
[CSDN 编者按]10 月 2 日,国际事务处理性能委员会公布了数据库最新性能测试结果,在 TPC-C 基准测试中,由阿里巴巴集团蚂蚁金服自主研发的分布式关系数据库 OceanBase 打破了由 O ...
- 宁愿“大小周”、每天只写 200 行代码、月薪 8k-17k 人群再涨!揭晓中国开发者真实现状...
作者 | 郑丽媛 出品 | CSDN(ID:CSDNnews) 程序员,一个圈外人羡慕.圈内人喊苦的"神奇"职业--高薪.福利好是旁人羡慕的理由,高压.加班多却也是他们最常见的写照 ...
- js websocket同步等待_WebSocket硬核入门:200行代码,教你徒手撸一个WebSocket服务器...
本文原题"Node.js - 200 多行代码实现 Websocket 协议",为了提升内容品质,有较大修订. 1.引言 最近正在研究 WebSocket 相关的知识,想着如何能自 ...
- 宁愿“大小周”、每天只写 200 行代码、月薪 8k-17k 人群再涨 | 揭晓中国开发者真实现状
作者 | 郑丽媛 出品 | CSDN(ID:CSDNnews) 程序员,一个圈外人羡慕.圈内人喊苦的"神奇"职业--高薪.福利好是旁人羡慕的理由,高压.加班多却也是他们最常见的写照 ...
- 第一行代码 Hello world 背后的逻辑
第一行代码 Hello world 背后的逻辑 计算机俗称电脑,只不过它是一种通过通电来模拟人脑的工具,是一种可以进行数学.逻辑运算,还具有存储记忆功能的智能设备,目前是人类的小助手.计算机是以为 ...
- 爬虫python代码-Python爬虫教程:200行代码实现一个滑动验证码
Python爬虫教程:教你用200行代码实现一个滑动验证码 做网络爬虫的同学肯定见过各种各样的验证码,比较高级的有滑动.点选等样式,看起来好像挺复杂的,但实际上它们的核心原理还是还是很清晰的,本文章大 ...
- blockchain 区块链200行代码:在JavaScript实现的一个简单的例子
blockchain 区块链200行代码:在JavaScript实现的一个简单的例子 了解blockchain的概念很简单(区块链,交易链块):它是分布式的(即不是放置在同一台机器上,不同的网络设备上 ...
- JavaScript开发区块链只需200行代码
JavaScript开发区块链只需200行代码 用JavaScript开发实现一个简单区块链.通过这一开发过程,你将理解区块链技术是什么:区块链就是一个分布式数据库,存储结构是一个不断增长的链表,链表 ...
- 不到 200 行代码,教你如何用 Keras 搭建生成对抗网络(GAN)
不到 200 行代码,教你如何用 Keras 搭建生成对抗网络(GAN) 生成对抗网络(Generative Adversarial Networks,GAN)最早由 Ian Goodfello ...
最新文章
- Matlab:Matlab中常用的函数、案例详细攻略
- C语言反转二叉树的递归和迭代解决方案(附完整源码)
- SpringBoot高级-消息-AmqpAdmin管理组件的使用
- onchange事件与onpropertychange事件的区别
- python当用户输入的不是整数_当用户输入字符串而不是整数时,如何保护我的python代码?...
- superviseddescent (SDM C++11实现)环境配置
- sed的高级命令和软件包管理器rpm
- 多取值离散型特征工程_特征工程(完)
- VMware NAT模式设置静态IP(可上网)
- avalon2学习教程11数据联动
- Json.NET特殊处理64位长整型数据
- SQL Server 获取日期是星期周几(默认从周日开始到周六 1-7)
- Mysql两个引擎对比
- windows 上面的tensorflow-GPU、cuda、cudnn 安装
- pdf资源有密码怎么办?
- 运维安全操作建议规范手册
- GrassCutter使用教程
- 网站SEO优化知识梳理
- Place Holder 方法
- pycharm中导入模块