Linux下一种高效多定时器实现

作者:LouisozZ

日期:2018.08.29

运行环境说明

由于在 Linux 系统下一个进程只能设置一个时钟定时器,所以当应用需要有多个定时器来共同管理程序运行时,就需要自行实现多定时器管理。

本文就是基于这种需求,在实际编码工作的基础上总结而出,希望跟大家共享,也欢迎大家讨论指正。

多定时器原理

在一个进程只能有一个定时器的前提条件下,想要实现多定时器,就得用到那个进程能利用的唯一的定时器,这个定时器是由操作系统提供的,通过系统提供的接口来设置,常用的有 alarm() 和 setitimer(),不论用什么,后文统一称作系统定时接口,这两个接口的区别在很多博客里都有,不怎么清楚的可以自行搜索,这里就不再赘述(我比较懒,打字多对肾不好)。通过它们产生的定时信号作为基准时间,来管理实现多定时器。

举个栗子,糖炒板栗。利用系统定时接口设置了基准定时器,基准定时器每秒产生一个 SIGALRM 信号(系统时钟的超时时间到了之后会向进程发送信号以通知定时超时,alarm() 和 setitimer() 都是向进程发送 SIGALRM 信号,关于 Linux ‘信号’ 的内容,可以参考 《UNIX环境高级编程》),产生两个 SIGALRM 信号的时间间隔,就是多定时器的基准时间。当然,上述的基准时间是一秒,如果你是每隔 50ms 产生一个 SIGALRM 信号,那么多定时器的基准时间就是 50ms 。当有了基准时间之后,就可以对它进行管理,可以设置多个定时任务,现有两个定时任务,Timer1_Task , Timer2_Task, 其中 Timer1_Task 的定时时长为 10 个基准时间,Timer2_Task 为 15 个基准时间,则每产生 10 个 SIGALRM 信号,就表示 Timer1_Task 定时器超时到达,执行一次 Timer1_Task 的超时任务,每产生 15 个 SIGALRM 信号,则执行一次 Timer2_Task 超时任务,当产生的 SIGALRM 信号个数是 30 (10 和 15 的最小公倍数),则 Timer1_Task 和 Timer2_Task 的超时任务都要被执行。

好了,原理讲完了,下面就是本文的重点了。

————————————— 说重点专用阉割线 —————————————

————————————— 说重点专用分割线 —————————————

高效多定时器

数据结构

由一个全局链表 g_pTimeoutCheckListHead 来管理超时任务。链表的每个节点是一个 tMultiTimer 结构体:

  1. typedef void TimeoutCallBack(void*); //回调函数格式
  2. typedef struct tMultiTimer
  3. {
  4. uint8_t nTimerID; //设置宏定义
  5. uint32_t nInterval; //定时时长
  6. uint32_t nTimeStamp; //时间戳
  7. bool bIsSingleUse; //是否单次使用
  8. bool bIsOverflow; //用于解决计数溢出问题
  9. TimeoutCallBack *pTimeoutCallbackfunction; //回调函数
  10. void* pTimeoutCallbackParameter; //回调函数参数
  11. struct tMultiTimer* pNextTimer; //双向链表后驱指针
  12. struct tMultiTimer* pPreTimer; //双向链表前驱指针
  13. struct tMultiTimer* pNextHandle; //二维链表相同超时Timer节点
  14. }tMultiTimer;
  15. tMultiTimer* g_pTimeoutCheckListHead; //管理多定时器的全局链表
  16. bool g_bIs_g_nAbsoluteTimeOverFlow; //基准时间计次器溢出标志位
  17. uint32_t g_nAbsoluteTime; //基准时间计次器
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

(各个成员变量的意义在后文会逐一介绍,客官莫急)

这个是一个二维双向链表,第一维根据时间戳,即绝对时间,按照先后顺序连接每一个 tMultiTimer 节点,当有多个超时任务的超时时刻是相同的时候,只有一个节点位于第一维,其余接在上一个相同超时时刻 tMultiTimer 节点的 pNextHandle 上,图示如下:

多定时管理流程

超时检测与运行

首先需要调用系统定时接口,设置进程的定时器,产生 SIGALRM 信号,每一次 SIGALRM 到来时,全局的基准时间计次器
g_nAbsoluteTime 自加,由于 g_nAbsoluteTime 是无符号类型,当其溢出时,是回到 0 ,每次溢出就把
g_bIs_g_nAbsoluteTimeOverFlow 取反。

  1. 每个 tMultiTimer 节点都有
  2. uint32_t nInterval; //定时时长
  3. uint32_t nTimeStamp; //时间戳
  • 1
  • 2
  • 3
  • 4

其中 nTimeStamp 这个值,是由 nInterval + g_nAbsoluteTime 计算而来,在把这个节点加入到全局链表的时刻计算的 ,这个和作为超时的绝对时间保存在结构体中,当计算的和溢出时,bIsOverflow 取反。通过这两个溢出标志位,可以用来解决溢出之后判断是否超时的问题,具体如下:

每一次基准时间超时,就检查链表的第一个节点的超时时间 nTimeStamp 是否小于全局绝对时间 g_nAbsoluteTime ,如果 g_bIs_g_nAbsoluteTimeOverFlow 与 bIsOverflow 不相等,则链表第一个节点的超时时间一定未到达,因为 bIsOverflow 的取反操作一定是先于 g_bIs_g_nAbsoluteTimeOverFlow ,如果一样则比较数值大小(初始化的时候两个溢出标志位是一样的)。当全局绝对时间大于等于第一个节点的时间戳,则把该节点及其 pNextHandle 指向的第二维链表取下,并更新 g_pTimeoutCheckListHead,然后依次执行所取下链表的回调函数。执行完之后(或者之前,根据实际情况定),判断 bIsSingleUse 成员变量,如果为 true 则表示是单次的计数器,仅执行一次,执行完回调之后则定时任务完成。如果是 false ,怎表示是定时任务,则重新执行一次添加超时任务。(添加超时任务看下一节)

添加超时任务

添加超时任务(添加一个 tMultiTimer 节点到全局链表 g_pTimeoutCheckListHead 中)的时候,指定超时时长,即间隔多少个基准时间,赋值给这个任务的成员变量 nInterval ,然后计算

  1. nTimeStamp = nInterval + g_nAbsoluteTime;
  2. if(nTimeStamp < g_nAbsoluteTime)
  3. bIsOverflow = ~bIsOverflow;
  • 1
  • 2
  • 3

接着搜索 g_pTimeoutCheckListHead ,如果有相同时间戳,则添加到其 pNextHandle 指向位置,如果没有相同时间戳节点,找到比要插入的节点时间戳大的节点,然后把当前节点插入到其前方。

对于链表中已经有相同 ID 的 tMultiTimer 节点的情况,再次添加则表示更新该定时任务,取消之前的定时任务重新插入到链表中。

取消超时任务

直接把对应 ID 的 tMultiTimer 节点从 g_pTimeoutCheckListHead 链表中摘掉即可。

效率分析

g_pTimeoutCheckListHead 链表中的 tMultiTimer 节点数量,是总共设置的超时任务数量,假设为 n,添加一个超时任务(节点)的最坏情况是遍历 n 个节点。

检查是否有任务超时所用的时间是常数时间,只检查第一个节点。

对于定时任务的再次插入问题,如果定时任务间隔时间越短,其反复被插入的次数越多,但是由于定时时间短,所以在链表中的插入位置也就越靠前,将快速找到插入点;如果定时任务间隔时间越长,越可能遍历整个链表在末尾插入,但是由于间隔时间长,重复插入的频率则很低。

与一种简单的定时器实现相比较:

  1. if(g_nAbsoluteTime % 4)
  2. {
  3. Timer_Task_1();
  4. }
  5. if(g_nAbsoluteTime % 17)
  6. {
  7. Timer_Task_2();
  8. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这种简单实现来说,

1:每次添加、取消一个定时任务都需要修改定时器源码,复用性不高。

2:每次检查是否有任务超时需要遍历 n 个定时任务。

关于多线程下的一些坑

我在实际项目中使用的环境是多线程的,有四个,我把多定时器管理放在了单独的一个线程里。由于系统定时器接口产生的信号是发送给进程的,所以所有的线程都共享这个闹钟信号。一开始我是这么想的,定时器的默认动作是杀死进程,那么给每个线程添加信号捕捉函数,这样的话闹钟信号到了之后不管是那个线程接管了,都能到我指定的处理函数去,可是实际情况并非如此,进程仍然会被杀死。

后面我用了线程信号屏蔽,把非定时器线程都设置了信号屏蔽字,即闹钟信号不被别的线程可见,这样才能正常运行,至于第一种方法为何不行,现在我还没有找到原因,还是对 Linux 的信号机制不熟,以后看有时间的话把这里搞懂吧。

main.c

  1. //配置信号集
  2. sigset_t g_sigset_mask;
  3. sigemptyset(&g_sigset_mask);
  4. sigaddset(&g_sigset_mask,SIGALRM);
  • 1
  • 2
  • 3
  • 4

other_thread.c

  1. sigset_t old_sig_mask;
  2. if(err = pthread_sigmask(SIG_SETMASK,&g_sigset_mask,&old_sig_mask) != 0)
  3. {
  4. // pthread_sigmask 设置信号屏蔽
  5. return ;
  6. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

mulitimer_thread

  1. void* MultiTimer_thread(void *parameter)
  2. {
  3. int err,signo;
  4. struct itimerval new_time_value,old_time_value;
  5. new_time_value.it_interval.tv_sec = 0;
  6. new_time_value.it_interval.tv_usec = 1000;
  7. new_time_value.it_value.tv_sec = 0;
  8. new_time_value.it_value.tv_usec = 1;
  9. setitimer(ITIMER_REAL, &new_time_value,NULL);
  10. for(;;)
  11. {
  12. err = sigwait(&g_sigset_mask,&signo);//信号捕捉
  13. if(err != 0)
  14. {
  15. return ;
  16. }
  17. if(signo == SIGALRM)
  18. {
  19. SYSTimeoutHandler(signo);
  20. }
  21. }
  22. return ((void*)0);
  23. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

multiTimer.c

  1. #include "multiTimer.h"
  2. /**
  3. * @function 把一个定时任务添加到定时检测链表中
  4. * @parameter 一个定时器对象,可以由全局变量 g_aSPPMultiTimer 通过 TIMER_ID 映射得到
  5. */
  6. static void AddTimerToCheckList(tMultiTimer* pTimer)
  7. {
  8. tMultiTimer* pEarliestTimer = NULL;
  9. tMultiTimer* pEarliestTimer_pre = NULL;
  10. CDebugAssert(pTimer->nInterval != 0);
  11. pTimer->nTimeStamp = g_nAbsoluteTime + pTimer->nInterval;
  12. if(pTimer->nTimeStamp < g_nAbsoluteTime)
  13. pTimer->bIsOverflow = !(pTimer->bIsOverflow);
  14. if(g_pTimeoutCheckListHead == NULL)
  15. {
  16. g_pTimeoutCheckListHead = pTimer;
  17. g_pTimeoutCheckListHead->pNextTimer = NULL;
  18. g_pTimeoutCheckListHead->pPreTimer = NULL;
  19. g_pTimeoutCheckListHead->pNextHandle = NULL;
  20. return;
  21. }
  22. else
  23. {
  24. pEarliestTimer = g_pTimeoutCheckListHead;
  25. while(pEarliestTimer != NULL)
  26. {
  27. //如果超时时间小于新加的timer则直接跳过;
  28. if((pEarliestTimer->bIsOverflow != pTimer->bIsOverflow) || (pEarliestTimer->nTimeStamp < pTimer->nTimeStamp))
  29. {
  30. pEarliestTimer_pre = pEarliestTimer;
  31. pEarliestTimer = pEarliestTimer->pNextTimer;
  32. }
  33. else
  34. {
  35. if(pEarliestTimer->nTimeStamp == pTimer->nTimeStamp) //超时时刻相等,直接添加到相同时刻处理列表的列表头
  36. {
  37. pTimer->pNextHandle = pEarliestTimer->pNextHandle;
  38. pEarliestTimer->pNextHandle = pTimer;
  39. return;
  40. }
  41. else //找到了超时时刻大于新加入timer的第一个节点
  42. {
  43. if(pEarliestTimer->pPreTimer == NULL) //新加入的是最早到达超时时刻的,添加到链表头
  44. {
  45. pEarliestTimer->pPreTimer = pTimer;
  46. pTimer->pNextTimer = pEarliestTimer;
  47. pTimer->pPreTimer = NULL;
  48. pTimer->pNextHandle = NULL;
  49. g_pTimeoutCheckListHead = pTimer;
  50. return;
  51. }
  52. else //中间节点
  53. {
  54. pEarliestTimer->pPreTimer->pNextTimer = pTimer;
  55. pTimer->pNextTimer = pEarliestTimer;
  56. pTimer->pPreTimer = pEarliestTimer->pPreTimer;
  57. pEarliestTimer->pPreTimer = pTimer;
  58. pTimer->pNextHandle = NULL;
  59. return;
  60. }
  61. }
  62. }
  63. }
  64. if(pEarliestTimer == NULL) //新加入的timer超时时间是最晚的那个
  65. {
  66. pEarliestTimer_pre->pNextTimer = pTimer;
  67. pTimer->pPreTimer = pEarliestTimer_pre;
  68. pTimer->pNextTimer = NULL;
  69. pTimer->pNextHandle = NULL;
  70. }
  71. return;
  72. }
  73. }
  74. /**
  75. * @function 设置一个定时任务,指定超时间隔与回调函数,当超时到来,自动执行回调
  76. * @parameter1 TIMER_ID
  77. * @parameter2 超时间隔时间
  78. * @parameter3 是否是一次性定时任务
  79. * @parameter4 回调函数,注意,回调函数的函数形式 void function(void*);
  80. * @parameter5 void* 回调函数的参数,建议用结构体强转成 void*,在回调函数中再强转回来
  81. * @return 错误码
  82. */
  83. uint8_t SetTimer(uint8_t nTimerID,uint32_t nInterval,bool bIsSingleUse,TimeoutCallBack* pCallBackFunction,void* pCallBackParameter)
  84. {
  85. printf("\nset timer %d\n",nTimerID);
  86. tMultiTimer* pChoosedTimer = NULL;
  87. pChoosedTimer = g_aSPPMultiTimer[nTimerID];
  88. pChoosedTimer->nInterval = nInterval;
  89. pChoosedTimer->bIsSingleUse = bIsSingleUse;
  90. pChoosedTimer->pTimeoutCallbackfunction = pCallBackFunction;
  91. pChoosedTimer->pTimeoutCallbackParameter = pCallBackParameter;
  92. //如果超时任务链表中已经有这个任务了,先取消,然后再设置,即重置超时任务
  93. if(pChoosedTimer->pNextTimer != NULL || pChoosedTimer->pPreTimer != NULL)
  94. CancelTimerTask(nTimerID,CANCEL_MODE_IMMEDIATELY);
  95. AddTimerToCheckList(pChoosedTimer);
  96. return 0;
  97. }
  98. /**
  99. * @function 取消超时检测链表中的指定超时任务
  100. * @parameter1 要取消的超时任务的ID
  101. * @parameter2 模式选择,是立即取消,还是下次执行后取消
  102. * @return 错误码
  103. */
  104. uint8_t CancelTimerTask(uint8_t nTimerID,uint8_t nCancelMode)
  105. {
  106. printf("\ncancle timer %d\n",nTimerID);
  107. tMultiTimer* pEarliestTimer = NULL;
  108. tMultiTimer* pHandleTimer = NULL;
  109. tMultiTimer* pHandleTimer_pre = NULL;
  110. tMultiTimer* pChoosedTimer = NULL;
  111. pEarliestTimer = g_pTimeoutCheckListHead;
  112. pChoosedTimer = g_aSPPMultiTimer[nTimerID];
  113. if(nCancelMode == CANCEL_MODE_IMMEDIATELY)
  114. {
  115. while(pEarliestTimer != NULL)
  116. {
  117. pHandleTimer = pEarliestTimer;
  118. pHandleTimer_pre = NULL;
  119. while(pHandleTimer != NULL)
  120. {
  121. if(pHandleTimer->nTimerID == nTimerID)
  122. {
  123. if(pHandleTimer_pre == NULL)
  124. {
  125. if(pHandleTimer->pNextHandle != NULL)
  126. {
  127. pEarliestTimer = pHandleTimer->pNextHandle;
  128. pEarliestTimer->pPreTimer = pHandleTimer->pPreTimer;
  129. if(pHandleTimer->pPreTimer != NULL)
  130. pHandleTimer->pPreTimer->pNextTimer = pEarliestTimer;
  131. pEarliestTimer->pNextTimer = pHandleTimer->pNextTimer;
  132. if(pHandleTimer->pNextTimer != NULL)
  133. pHandleTimer->pNextTimer->pPreTimer = pEarliestTimer;
  134. pHandleTimer->pNextTimer = NULL;
  135. pHandleTimer->pPreTimer = NULL;
  136. pHandleTimer->pNextHandle = NULL;
  137. }
  138. else
  139. {
  140. if(pEarliestTimer->pPreTimer == NULL)
  141. {
  142. g_pTimeoutCheckListHead = pEarliestTimer->pNextTimer;
  143. g_pTimeoutCheckListHead->pPreTimer = NULL;
  144. pEarliestTimer->pNextTimer = NULL;
  145. }
  146. else if(pEarliestTimer->pNextTimer == NULL)
  147. {
  148. pEarliestTimer->pPreTimer->pNextTimer = NULL;
  149. pEarliestTimer->pPreTimer = NULL;
  150. }
  151. else
  152. {
  153. pEarliestTimer->pPreTimer->pNextTimer = pEarliestTimer->pNextTimer;
  154. pEarliestTimer->pNextTimer->pPreTimer = pEarliestTimer->pPreTimer;
  155. pEarliestTimer->pPreTimer = NULL;
  156. pEarliestTimer->pNextTimer = NULL;
  157. }
  158. }
  159. }
  160. else
  161. {
  162. pHandleTimer_pre->pNextHandle = pHandleTimer->pNextHandle;
  163. pHandleTimer->pNextHandle = NULL;
  164. }
  165. return 0;
  166. }
  167. else
  168. {
  169. pHandleTimer_pre = pHandleTimer;
  170. pHandleTimer = pHandleTimer_pre->pNextHandle;
  171. }
  172. }
  173. pEarliestTimer = pEarliestTimer->pNextTimer;
  174. }
  175. #ifdef DEBUG_PRINTF
  176. printf("\nThere is no this timer task!\n");
  177. #endif
  178. return 2; //出错,超时检测链表中没有这个超时任务
  179. }
  180. else if(nCancelMode == CANCEL_MODE_AFTER_NEXT_TIMEOUT)
  181. {
  182. pChoosedTimer->bIsSingleUse = true;
  183. return 0;
  184. }
  185. else
  186. {
  187. return 1; //出错,模式错误,不认识该模式
  188. }
  189. }
  190. /**
  191. * @function 定时器处理函数,用于检测是否有定时任务超时,如果有则调用该定时任务的回调函数,并更新超时检测链表
  192. * 更新动作:如果超时的那个定时任务不是一次性的,则将新的节点加入到检测超时链表中,否则直接删掉该节点;
  193. * @parameter
  194. * @return
  195. */
  196. void SYSTimeoutHandler(int signo)
  197. {
  198. //printf("\nenter SYSTimeoutHandler\n");
  199. if(signo != SIGALRM)
  200. return;
  201. tMultiTimer* pEarliestTimer = NULL;
  202. tMultiTimer* pWaitingToHandle = NULL;
  203. tMultiTimer* pEarliestTimerPreHandle = NULL;
  204. if(g_pTimeoutCheckListHead != NULL)
  205. {
  206. if((g_pTimeoutCheckListHead->nTimeStamp <= g_nAbsoluteTime) && (g_pTimeoutCheckListHead->bIsOverflow == g_bIs_g_nAbsoluteTimeOverFlow))
  207. {
  208. pWaitingToHandle = g_pTimeoutCheckListHead;
  209. g_pTimeoutCheckListHead = g_pTimeoutCheckListHead->pNextTimer;
  210. if(g_pTimeoutCheckListHead != NULL)
  211. g_pTimeoutCheckListHead->pPreTimer = NULL;
  212. pWaitingToHandle->pNextTimer = NULL;
  213. pEarliestTimer = pWaitingToHandle;
  214. while(pEarliestTimer != NULL)
  215. {
  216. pEarliestTimerPreHandle = pEarliestTimer;
  217. pEarliestTimer = pEarliestTimer->pNextHandle;
  218. pEarliestTimerPreHandle->pNextHandle = NULL;
  219. pEarliestTimerPreHandle->pNextTimer = NULL;
  220. pEarliestTimerPreHandle->pPreTimer = NULL;
  221. pEarliestTimerPreHandle->pTimeoutCallbackfunction(pEarliestTimerPreHandle->pTimeoutCallbackParameter);
  222. if(!(pEarliestTimerPreHandle->bIsSingleUse))
  223. AddTimerToCheckList(pEarliestTimerPreHandle);
  224. }
  225. }
  226. }
  227. g_nAbsoluteTime++;
  228. if(g_nAbsoluteTime == 0)
  229. g_bIs_g_nAbsoluteTimeOverFlow = !g_bIs_g_nAbsoluteTimeOverFlow;
  230. return ;
  231. }
  232. void CancleAllTimerTask()
  233. {
  234. tMultiTimer* pEarliestTimer = NULL;
  235. tMultiTimer* pHandleTimer = NULL;
  236. while(g_pTimeoutCheckListHead != NULL)
  237. {
  238. pEarliestTimer = g_pTimeoutCheckListHead;
  239. g_pTimeoutCheckListHead = g_pTimeoutCheckListHead->pNextTimer;
  240. while(pEarliestTimer != NULL)
  241. {
  242. pHandleTimer = pEarliestTimer;
  243. pEarliestTimer = pEarliestTimer->pNextHandle;
  244. pHandleTimer->pNextHandle = NULL;
  245. pHandleTimer->pNextTimer = NULL;
  246. pHandleTimer->pPreTimer = NULL;
  247. pHandleTimer->bIsOverflow = false;
  248. }
  249. }
  250. g_bIs_g_nAbsoluteTimeOverFlow = false;
  251. g_nAbsoluteTime = 0;
  252. return;
  253. }
  254. void MultiTimerInit()
  255. {
  256. g_pTimeoutCheckListHead = NULL;
  257. g_bIs_g_nAbsoluteTimeOverFlow = false;
  258. g_nAbsoluteTime = 0;
  259. for(uint8_t index = 0; index < MAX_TIMER_UPPER_LIMIT; index++)
  260. {
  261. g_aSPPMultiTimer[index] = (tMultiTimer*)CMALLOC(sizeof(tMultiTimer));
  262. g_aSPPMultiTimer[index]->nTimerID = g_aTimerID[index];
  263. g_aSPPMultiTimer[index]->nInterval = g_aDefaultTimeout[index];
  264. g_aSPPMultiTimer[index]->nTimeStamp = 0;
  265. g_aSPPMultiTimer[index]->bIsSingleUse = true;
  266. g_aSPPMultiTimer[index]->bIsOverflow = false;
  267. g_aSPPMultiTimer[index]->pTimeoutCallbackfunction = NULL;
  268. g_aSPPMultiTimer[index]->pTimeoutCallbackParameter = NULL;
  269. g_aSPPMultiTimer[index]->pNextTimer = NULL;
  270. g_aSPPMultiTimer[index]->pPreTimer = NULL;
  271. g_aSPPMultiTimer[index]->pNextHandle = NULL;
  272. }
  273. /* 如果预先规定了一些定时器,这个时候可以初始化除时间戳以外的其他值 */
  274. //开启应答超时任务
  275. //OPEN_MULTITIMER_MANGMENT();
  276. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294

multiTimer.h

  1. #ifndef __MULTITIMER_H__
  2. #define __MULTITIMER_H__
  3. #define MAX_TIMER_UPPER_LIMIT 6
  4. #define TIMER_0 0
  5. #define TIMER_1 1 //timer ID
  6. #define TIMER_2 2
  7. #define TIMER_3 3
  8. #define TIMER_4 4
  9. #define TIMER_5 5
  10. #define CANCEL_MODE_IMMEDIATELY 0xf9
  11. #define CANCEL_MODE_AFTER_NEXT_TIMEOUT 0x9f
  12. typedef void TimeoutCallBack(void*);
  13. //========================================================
  14. // timer结构定义
  15. //========================================================
  16. typedef struct tMultiTimer
  17. {
  18. uint8_t nTimerID; //
  19. uint32_t nInterval; //定时时长
  20. uint32_t nTimeStamp; //时间戳
  21. bool bIsSingleUse; //是否单次使用
  22. bool bIsOverflow; //用于解决计数溢出问题
  23. TimeoutCallBack *pTimeoutCallbackfunction;
  24. void* pTimeoutCallbackParameter;
  25. //双向链表指针
  26. struct tMultiTimer* pNextTimer;
  27. struct tMultiTimer* pPreTimer;
  28. //相同时间戳的下一个处理函数 这里可能会有隐藏的 bug,如果基础时间中断比较快,那么可能在处理多个同一时间节点的
  29. //回调函数的时候被下一次的中断打断,这里会引起时序错误,
  30. //解决方案有三种,
  31. //一是可以人为避免,不设置有公约数的定时时间,这样的话同一个时刻有多个定时任务的情况就小很多;
  32. //二是回调函数尽量少做事,快速退出定时处理函数;
  33. //三是另开一个线程,这个线程仅把回调函数放到一个队列中,另一个线程持续从队列中取回调函数执行,这个是没有问题的方案,但是需要支持多线程或者多任务,并且需要注意加锁
  34. struct tMultiTimer* pNextHandle;
  35. }tMultiTimer;
  36. //========================================================
  37. // 实现多定时任务的相关变量
  38. //========================================================
  39. tMultiTimer* g_pTimeoutCheckListHead;
  40. bool g_bIs_g_nAbsoluteTimeOverFlow;
  41. uint32_t g_nAbsoluteTime;
  42. //========================================================
  43. // 外部接口
  44. //========================================================
  45. void MultiTimerInit();
  46. uint8_t SetTimer(uint8_t nTimerID,uint32_t nInterval,bool bIsSingleUse,TimeoutCallBack* pCallBackFunction,void* pCallBackParameter);
  47. uint8_t CancelTimerTask(uint8_t nTimerID,uint8_t nCancelMode);
  48. void CancleAllTimerTask();
  49. void SYSTimeoutHandler(int signo);
  50. #endif

Linux下一种高效多定时器实现的更多相关文章

  1. Linux下几种文件传输命令

    Linux下几种文件传输命令 sz rz sftp scp 最近在部署系统时接触了一些文件传输命令,分别做一下简单记录: 1.sftp Secure Ftp 是一个基于SSH安全协议的文件传输管理工具 ...

  2. Linux下几种文件传输命令 sz rz sftp scp

    Linux下几种文件传输命令 sz rz sftp scp 最近在部署系统时接触了一些文件传输命令,分别做一下简单记录: 1.sftp Secure Ftp 是一个基于SSH安全协议的文件传输管理工具 ...

  3. [原创] Linux下几种文件传输命令 sz rz sftp scp介绍

    Linux下几种文件传输命令 sz rz sftp scp介绍 1.sftp Secure Ftp 是一个基于SSH安全协议的文件传输管理工具.由于它是基于SSH的,会在传输过程中对用户的密码.数据等 ...

  4. Linux下几种重启Nginx的方式,找出nginx配置文件路径和测试配置文件是否正确

    Linux下几种重启Nginx的方式,找出nginx配置文件路径和测试配置文件是否正确 目录在/etc/ngnix/conf.d下找出nginx配置文件路径和测试配置文件是否正确# /usr/sbin ...

  5. Linux下几种常见压缩方式测试对比

    目录 Linux下几种常见压缩方式测试对比 参考 简介 测试 总结 Linux下几种常见压缩方式测试对比

  6. Linux 下三种提高工作效率的文件处理技巧

    Linux 下三种提高工作效率的文件处理技巧 在 Linux 下工作,打交道最多的就是文件了,毕竟 Linux 下工作一切皆文件嘛.Linux 也为大家提供了多种用于处理文件的命令,合理使用这些命令可 ...

  7. Linux下6种优秀的邮件传输代理

    导读 在互联网上,邮件客户端向邮件服务器发送邮件然后将消息路由到正确的目的地(其他客户),其中邮件服务器使用的一个网络应用程序称为邮件传输代理(MTA). 最好的Linux邮件传输代理(MTAs) 邮 ...

  8. Linux下9种优秀的代码比对工具推荐

    大家好,我是良许. 在我们编写代码的时候,我们经常需要知道两个文件之间,或者同一个文件不同版本之间有什么差异性.在 Windows 下有个很强大的工具叫作 BeyondCompare ,那在 Linu ...

  9. Linux下5种IO模型的小结

    概述 接触网络编程,我们时常会与各种与IO相关的概念打交道:同步(Synchronous).异步(ASynchronous).阻塞(blocking)和非阻塞(non-blocking).关于概念的区 ...

随机推荐

  1. oracle insert into 多条数据

    mysql : insert into tablename (column1,column2) values ('aa','bb'), ('dd','cc'), ('ee','ff'); oracle ...

  2. PHP中部分宏应用

    1.字符串复制 ZVAL_STRINGL(pzv, str, len, dup):str 和 len 分别为内存中保存的字符串地址和他的长度,dup之名该字符串是否需要被复制,值为1则将先申请一块新内 ...

  3. python与统计(龙族版)

    因为我很喜欢龙族,额........我也很喜欢python这门语言.然后就结合了一下,用python了解了一下龙族四本书的人物出场次数及排名. <龙族1火之晨曦> 路明非 1877 诺诺 ...

  4. vagrant系列三:vagrant搭建的php7环境

    原文:https://blog.csdn.net/hel12he/article/details/51107236 前面已经把vagrant的基础知识已经基本过了一遍 了,相信只要按着教程来,你已经搭 ...

  5. Flutter移动电商实战 --(35)列表页_上拉加载更多制作

    右侧列表上拉加载配合类别的切换 上拉加载需要一个page参数,当点击大类或者小类的时候,这个page就要变成1 provide内定义参数 首先我们需要定义一个page的变量 下图是我们之前在首页的时候 ...

  6. CPU、io、mem之间的关系

    https://blog.csdn.net/weixin_38250126/article/details/83412749 https://blog.csdn.net/joeyon1985/arti ...

  7. 19.网络插件calico

    19.网络插件calico 官网: https://docs.projectcalico.org/v3.8/introduction/ calico默认工作在192.168.0.0/16 的网络 ca ...

  8. apache定制错误页面

    编辑配置文件,错误页面定制支持三种形式: 1. 普通文本 2. 本地跳转 3. 外部跳转 [root@ken-node2 ~]# vim /etc/httpd/conf/httpd.conf ... ...

  9. Json_DataMember签名作用

    [DataContract] public class ApiResponse { [DataMember] public string Time { get; set; } } 序列化和反序列,如果 ...

  10. python中hashlib模块用法示例

    python中hashlib模块用法示例 我们以前介绍过一篇Python加密的文章:Python 加密的实例详解.今天我们看看python中hashlib模块用法示例,具体如下. hashlib ha ...