前言:
此篇文章主要是C语言基于二叉堆实现定时任务处理功能,寻找相对快的方法处理任务堆。针对定时任务,可设置参数有回调函数(
callback
),事件周期(period
),是否持续执行(persist
), 回调函数参数(*p
)。
一、设计思路。
试想一下,我们要设计一个定时任务堆存储各个事件任务的数据,其中包括前言中提到的可设置参数,首先想到的就是给系统时间做一个坐标轴,每个定时任务以当前时间为基准,加上定时任务本身的周期,然后在坐标轴上标记一个点。这样系统时间不断往下走,执行到定时任务设置的时间点时执行任务,若当前任务是持续执行的则再在当前时间之后(间隔为任务执行周期)再标记一个时间点,若此任务不是持续执行,则直接舍弃。
如上图所示,假设系统当前时间是a
点,现在把Task1
放在b
点,然后系统时间开始往下走,走到b
点的时候执行Task1
,此时判断Task1
是不是需要循环执行,若是再把Task1
放到设定的时间间隔之后的c
点。
具体的实现流程如下图所示:
二、实现方法。
这一小节对比一下几种方法的时间复杂度,可略过直接看第三小节代码部分。
- 【数组法】定义一个结构体,将每个任务事件的参数放到结构体里,然后创建一个结构体数组,将超时时间点从小到大排序,每次处理当前时间可执行任务时对比最小的超时时间和当前时间确认是否执行任务。添加任务的时候,需要先确认当前添加任务的位置,然后将当前位置以及后面的每个任务数据都往后移动一个位置,然后把添加的任务加进来。假设查找添加任务应该存放的位置时使用二分法,则查找到的时间复杂度是:
O
(
l
o
g
2
N
)
O(log_2N)
O(log2N),则移动的时间复杂度是
O
(
N
)
O(N)
O(N).
所以添加任务事件的时间复杂度是 O ( N + l o g 2 N ) O(N + log_2N) O(N+log2N)查询的任务复杂度是 O ( 1 ) O(1) O(1)删除任务考虑到需要将后面事件往前移动,其复杂度是 O ( N ) O(N) O(N) - 【链表法】同样是利用结构体,然后把所有时间的超时时间按从大到小构建成一个单向链表,每次添加任务的时候需要根据当前任务的超时时间找到合适的位置,然后添加进去。
所以添加任务的时间复杂度是 O ( N ) O(N) O(N)查询和删除(这里只考虑删除第一个元素)的时间复杂度是 O ( 1 ) O(1) O(1) - 【二叉堆法】二叉堆顾名思义就是一个堆,也就是一个数组但这是一个特别的数组,是一个完全二叉树(就是允许最后一行没有子树)。其图如下:
每个圆圈上面的数字代表在数组中的位置(从1开始),圆圈的内容是当前位置的数据,叫键值,上面的圆圈是与其相连的下面圆圈的双亲,可以叫做父节点,下面的圆圈则是上面圆圈的孩子,可以叫做子节点。比如上图,1是2和3的父节点,2和3又分别是4、5和6、7的父节点,然后他们又分别是其父节点的子节点。
二叉堆又分为最大堆和最小堆,最小堆的规则是父节点的键值要比子节点小,最大堆则相反。这样我们可以想到,对于最小堆(最大堆)其堆顶的键值是整个堆的最小值(最大值)。
现在以最小二叉堆为例介绍一下关于二叉堆的查找、增加、删除。
查找的话就是找堆顶了,就是数组中1位置的元素,其时间复杂度是 O ( 1 ) O(1) O(1)
增加时需要先把增加的元素放到当前堆的最后,然后将这个元素和其父节点比较,若小于父节点,这样循环至其大于父节点或者交换到了堆顶位置。增加元素的时间复杂度是 O ( l o g 2 N ) O(log_2N) O(log2N)
删除元素时,首先把要删除的元素和数组最后一个元素交换,释放掉最后一个元素,然从删除元素的那个位置开始比较其与子节点中比较小的那个的大小关系,若大于子节点则交换,这样以此类推直到该元素小于其子节点中比较小的那个或者其没有子节点。时间复杂度也是 O ( l o g 2 N ) O(log_2N) O(log2N)比如要删除上图1位置的元素,流程如下:
从上面时间复杂度对比来看,二叉堆是最优选择。
三、代码实现
//file:timer.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "timer.h"
#define TIMER_CAPACITY_INIT (int)10 //初始二叉堆容量
static _st_bstree_timer *bsTree_timer;
static int bstree_timer_capacity; //二叉堆容量
static int bstree_timer_size; //二叉堆元素个数
static int now_time; //当前系统时间
//交换元素
static void timer_swap(_st_bstree_timer *st1, _st_bstree_timer *st2)
{
if(!st1 || !st2)
{
printf("swap error\r\n");
return;
}
_st_bstree_timer temp;
memset(&temp, 0, sizeof(_st_bstree_timer));
temp = *st1;
*st1 = *st2;
*st2 = temp;
}
//添加元素时维护二叉堆向上浮动
static void timer_flow_up(void)
{
if(bstree_timer_size <= 1) return;
int now = bstree_timer_size;//从最后一个元素开始
while(now) //找到堆顶退出循环
{
if(bsTree_timer[now].timeout_ms < bsTree_timer[now/2].timeout_ms) //当前元素小于父节点则交换
{
timer_swap(&bsTree_timer[now], &bsTree_timer[now/2]);
now = now/2;
}else break; //大于父节点退出循环
}
}
//删除元素时维护二叉堆向下浮动
static void timer_flow_down(int pos)
{
if(pos < 1) return;
int now = pos * 2; //删除位置的左孩子
int start = pos;
timer_swap(&bsTree_timer[pos], &bsTree_timer[bstree_timer_size]);//先交换第一个元素和最后一个元素
while(now <= bstree_timer_size)//找到最后一个元素时结束
{
//比较左孩子和右孩子找到比较小的那个
if((now + 1) < bstree_timer_size && bsTree_timer[now].timeout_ms > bsTree_timer[now + 1].timeout_ms)
now ++;
//若大于较小的子节点则交换
if(bsTree_timer[start].timeout_ms > bsTree_timer[now].timeout_ms)
{
timer_swap(&bsTree_timer[start], &bsTree_timer[now]);
}else break;//小于等于较小的子节点时推出循环
start = now;
now = now * 2;
}
}
static _st_bstree_timer timer_pop(int pos)
{
_st_bstree_timer timer_temp;
memset(&timer_temp, 0, sizeof(_st_bstree_timer));
if(pos < 1) return(_st_bstree_timer) timer_temp;
memcpy(&timer_temp, &bsTree_timer[pos], sizeof(_st_bstree_timer));
timer_flow_down(pos);
//清除最后一个元素
memset(bsTree_timer + bstree_timer_size, 0, sizeof(_st_bstree_timer));
bstree_timer_size--;
return timer_temp;
}
void bstree_timer_init(void)
{
bsTree_timer = (_st_bstree_timer *) malloc(sizeof(_st_bstree_timer) * (TIMER_CAPACITY_INIT + 1));
memset(bsTree_timer, 0, sizeof(_st_bstree_timer)* (TIMER_CAPACITY_INIT + 1));
bstree_timer_capacity = TIMER_CAPACITY_INIT;
}
void bstree_timer_add(_st_bstree_timer timer)
{
if (bstree_timer_size >= bstree_timer_capacity - 1)
{
//容量不足扩充容量
bstree_timer_capacity = bstree_timer_capacity + bstree_timer_capacity/3;
bsTree_timer = (_st_bstree_timer *)realloc(bsTree_timer, sizeof(_st_bstree_timer) * (bstree_timer_capacity + 1));
}
bstree_timer_size++;
timer.timeout_ms = timer.period + now_time;
bsTree_timer[bstree_timer_size] = timer;
if(bstree_timer_size > 1)
{
timer_flow_up();
}
}
void bstree_timer_handle(void)
{
while(1)//循环执行完所有任务
{
if(bstree_timer_size < 1) return;
//当前时间到了事件设定的超时时间则执行
if(bsTree_timer[1].timeout_ms <= now_time)
{
_st_bstree_timer timer_temp = timer_pop(1);
if(!timer_temp.callback) return;
timer_temp.callback(timer_temp.p);
//判断是否循环执行
if(timer_temp.persist)
{
timer_temp.timeout_ms = now_time + timer_temp.period;
bstree_timer_add(timer_temp);
}
}else return;
}
}
void bstree_timer_base_time_sync(bstree_timer_t cnt)
{
now_time += cnt;
}
bstree_timer_t bstree_timer_get_base_time(void)
{
return now_time;
}
//file:timer.h
# ifndef USER_TIMER_H_
# define USER_TIMER_H_
typedef unsigned long bstree_timer_t;
typedef void (*user_timer_callback)(void *);
typedef struct
{
int persist : 1;
int period;
bstree_timer_t timeout_ms;
user_timer_callback callback;
void *p;
}_st_bstree_timer;
extern void bstree_timer_init();
extern void bstree_timer_add(_st_bstree_timer tiemr);
extern void bstree_timer_base_time_sync(bstree_timer_t cnt);
extern bstree_timer_t bstree_timer_get_base_time(void);
extern void bstree_timer_handle(void);
#endif
//file:test.c
#include <stdio.h>
#include <unistd.h>
#include "timer.h"
void task(void *p)
{
char *data = (char *)p;
printf("hello %s\r\n", data);
}
int main()
{
bstree_timer_init();
_st_bstree_timer st[3];
int i = 0;
st[i].persist = 1;
st[i].callback = task;
st[i].period = 1000;
st[i].p = (void *)"task0";
i++;
st[i].persist = 1;
st[i].callback = task;
st[i].period = 2000;
st[i].p = (void *)"task1";
i++;
st[i].persist = 0;
st[i].callback = task;
st[i].period = 5000;
st[i].p = (void *)"task3";
i++;
for(int j = 0; j < i; j++)
{
bstree_timer_add(st[j]);
}
while(1)
{
usleep(1000);
bstree_timer_handle();
bstree_timer_base_time_sync(1);//这里基准时间是1ms为单位,可自行修改
}
}
四、小结
本篇到这里就结束了,涉及理论部分个人理解难免有所出入,文中如有错误之处请帮忙指正。另外,代码还有部分细节需要考虑,可酌情修改。