基于setitimer实现允许单进程多次调用的定时器

时间:2022-09-18 23:31:29

原理

setitimer不能在进程中多次调用,考虑用链表管理调用。每个节点包含当前事件还剩余多少时间,回调等,每次有新节点时,重新更新节点时间。在计数到来后,重新恢复节点初始时间实现重复计时,删除相对简单,挪掉节点即可。

函数原型

int set_timer(struct own_timer **t, struct timeval val, callback* cb, void* arg) ;
int free_timer(struct own_timer* timer) ;

源码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <sys/time.h>
#include <pthread.h>


#define MIN(a,b) (((a) < (b))?(a):(b))




typedef void (*callback)(void* argc);

typedef struct list {
void* next;
void* prev;
} LIST_ENTRY;

struct own_timer {
LIST_ENTRY entry;
int handle;
struct timeval left_dyn;
struct timeval left_init;
callback* cb;
void* cb_arg;
};

static LIST_ENTRY *head = NULL;
static LIST_ENTRY *tail = NULL;
static struct timeval last_set;
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static int test = 0;

static struct own_timer *t1 = NULL;
static struct own_timer *t2 = NULL;
static struct own_timer *t3 = NULL;


void timeout(void* arg)
{
printf("timeout %d >>\n",(int)arg);
}

void timeout4(void* arg)
{
printf("timeout4 >>>>>>>>>\n");
test ++;
}

static int compare_time(struct timeval a, struct timeval b)
{
if(a.tv_sec > b.tv_sec)
return 1;
else if(a.tv_sec == b.tv_sec && a.tv_usec > b.tv_usec)
return 1;
else if(a.tv_sec == b.tv_sec && a.tv_usec == b.tv_usec)
return 0;
else
return -1;
}

static int diff_time(struct timeval* result, struct timeval* old,struct timeval* newt)
{
if(compare_time(*old,*newt) < 0) {
//printf("old shoud bigger\n");
return -1;
}
if((old->tv_sec - newt->tv_sec) >= 1) {
old->tv_sec --;
old->tv_usec += 1000000;
}

result->tv_sec = old->tv_sec - newt->tv_sec;
result->tv_usec = old->tv_usec - newt->tv_usec;

if(result->tv_usec >= 1000000) {
result->tv_usec -= 1000000;
result->tv_sec ++;
}
return 0;
}

void sig_alarm(int sig)
{
if(sig == SIGALRM) {
LIST_ENTRY* tmp;
struct own_timer* ptr =NULL;
struct own_timer* ptr_min2nd = NULL;
struct timeval min;
struct timeval min_2nd;
callback* min_cb;
void* min_arg;
//printf("lock in sig_alarm\n");
pthread_mutex_lock(&mutex);
//printf("SIG_ALARM\n");
//遍历结点,找到left时间最小的, 可能有多个.一定是它发生了
if (head == NULL) {
//printf("free in sig_alarm\n");
pthread_mutex_unlock(&mutex);
//printf("no nodes in list\n");
return;
} else {

//printf("find min...\n");
tmp = head->next;
ptr = (struct own_timer*)tmp;
min = ptr->left_dyn;
do {
ptr = (struct own_timer*)tmp;
//printf("min(%d %d) cur(%d %d)\n",(int)min.tv_sec,(int)min.tv_usec,(int)ptr->left_dyn.tv_sec,(int)ptr->left_dyn.tv_usec);
if(compare_time(min,ptr->left_dyn) > 0) {
min = ptr->left_dyn;
}
tmp = tmp->next;
}while(tmp != tail);

pthread_t tid;
tmp = head->next;
ptr = (struct own_timer*)tmp;
min_2nd = ptr->left_dyn;
ptr_min2nd = ptr;
//printf("excute min and find 2nd min ...\n");
do {
ptr = (struct own_timer*)tmp;

diff_time(&ptr->left_dyn,&ptr->left_dyn,&min);
//printf("left_dyn(%d %d)\n",(int)ptr->left_dyn.tv_sec,(int)ptr->left_dyn.tv_usec);
if (ptr->left_dyn.tv_usec == 0 && ptr->left_dyn.tv_sec == 0) {
//printf("handle = %x\n",ptr->handle);
//回调
if(ptr->cb) {
pthread_create(&tid,NULL,ptr->cb,ptr->cb_arg);
//回调里可能链表已经释放了
}
//重新计时
ptr->left_dyn = ptr->left_init;
//printf("reset left_dyn(%d %d)\n",(int)ptr->left_dyn.tv_sec,(int)ptr->left_dyn.tv_usec);
}
//不等于0的节点中寻找最小的
if(ptr->left_dyn.tv_sec != 0 || ptr->left_dyn.tv_usec != 0) {
if(compare_time(min_2nd,ptr->left_dyn) > 0) {
min_2nd = ptr->left_dyn;
ptr_min2nd = ptr;
}
}
tmp = tmp->next;
}while(tmp != tail);
}
//开始min_2nd setitimer
//printf("min_2nd (%d %d)\n",(int)ptr_min2nd->left_dyn.tv_sec,(int)ptr_min2nd->left_dyn.tv_usec);
struct itimerval newt;
struct timeval interval;
interval.tv_sec = 0;
interval.tv_usec = 0;
newt.it_value = ptr_min2nd->left_dyn;
newt.it_interval = interval;

setitimer(ITIMER_REAL,&newt,NULL);
last_set = ptr_min2nd->left_dyn;
}

//printf("free in sig_alarm\n");
pthread_mutex_unlock(&mutex);
}

int free_timer(struct own_timer* timer)
{
LIST_ENTRY* tmp;
LIST_ENTRY* tmp_prev;
LIST_ENTRY* tmp_next;
struct own_timer* ptr;
tmp = head->next;
ptr = (struct own_timer*)tmp;
//printf("lock in free_timer\n");
pthread_mutex_lock(&mutex);
do {
ptr = (struct own_timer*)tmp;

if (timer->handle == ptr->handle) {
//printf("found the node timer need free\n");
tmp_prev = tmp->prev;
tmp_next = tmp->next;
tmp_prev->next = tmp->next;
tmp_next->prev = tmp->prev;
break;
}

tmp = tmp->next;
} while(tmp != tail);

#if 1
if(timer) free(timer);
if(head->next == tail) {
if(head) {
free(head);
head = NULL;
}
if(tail) {
free(tail);
tail = NULL;
}
}
#endif
//printf("free in free_timer\n");
pthread_mutex_unlock(&mutex);

return 0;
}

int set_timer(struct own_timer **t, struct timeval val, callback* cb, void* arg)
{
struct itimerval newt;
struct itimerval oldt;
struct timeval set,left,passed;
struct timeval interval;
LIST_ENTRY* tmp;
struct own_timer* ptr =NULL;
sigset_t news,olds;
//节点内存分配
(*t) = (struct own_timer*)malloc(sizeof(struct own_timer));
if(*t == NULL) {
perror("malloc fail");
return -1;
}

(*t)->left_dyn = (*t)->left_init = val;
(*t)->cb = cb;
(*t)->cb_arg = arg;
(*t)->handle = 0xFFFFFFFF & rand();
//printf("new handle = %x\n",*t->handle);
//屏蔽SIGALRM ,比较粗糙
sigemptyset(&news);
sigaddset(&news,SIGALRM);
sigprocmask(SIG_BLOCK,&news,&olds);
//锁
//printf("lock in set_timer\n");
pthread_mutex_lock(&mutex);

interval.tv_sec = 0;
interval.tv_usec =0;
newt.it_interval = interval;
//获得从上次setitimer到现在,计时器还剩多少时间.
getitimer(ITIMER_REAL,&oldt);
left = oldt.it_value;
//刚开始
if(left.tv_sec == 0 && left.tv_usec == 0 && head == NULL) {
//printf("setitimer 1st start\n");
newt.it_value = val ;
setitimer(ITIMER_REAL,&newt,NULL);
last_set = val;
head = (LIST_ENTRY*)malloc(sizeof(LIST_ENTRY));
tail = (LIST_ENTRY*)malloc(sizeof(LIST_ENTRY));
//插入第1个节点
head->next = &((*t)->entry);
(*t)->entry.prev = head;
(*t)->entry.next = tail;
tail->prev = *t;

head->prev = tail;
tail->next = head;
} else if(left.tv_sec == 0 && left.tv_usec == 0 && head != NULL) {

//printf("setitimer timer stop\n");
newt.it_value = val ;
setitimer(ITIMER_REAL,&newt,NULL);
last_set = val;
tmp = head->next;
do {
tmp = tmp->next;
}while(tmp != tail);
//顺序插入节点
tmp = tmp->prev;

tmp->next = &((*t)->entry);
(*t)->entry.prev = tmp;
(*t)->entry.next = tail;
tail->prev = *t;

} else {
//printf("setitimer timer running\n");
//换算成消耗的时间. last_set是在setitimer后保存的timeval
diff_time(&passed,&last_set,&left);
//printf("passed time(%d %d)\n",(int)passed.tv_sec,(int)passed.tv_usec);
//剩余时间比当前想设置的计时大,重设置计时为当前计时。
if(compare_time(left,val) >= 0 ) {
newt.it_value = val ;
setitimer(ITIMER_REAL,&newt,NULL);
}
//每个结点需要减已消耗的时间。
tmp = head->next;
do {
ptr = (struct own_timer*)tmp;
//printf("handle %x:\n",ptr->handle);
//printf("before diff left_dyn(%d %d)\n",(int)ptr->left_dyn.tv_sec,(int)ptr->left_dyn.tv_usec);
diff_time(&ptr->left_dyn,&ptr->left_dyn,&passed);
//printf("after diff left_dyn(%d %d)\n",(int)ptr->left_dyn.tv_sec,(int)ptr->left_dyn.tv_usec);

tmp = tmp->next;

}while(tmp != tail);

//尾部插入节点
tmp = tmp->prev;

tmp->next = &((*t)->entry);
(*t)->entry.prev = tmp;
(*t)->entry.next = tail;
tail->prev = *t;
}

//恢复
sigprocmask(SIG_SETMASK,&olds,NULL);
//解锁
//printf("free in set_timer\n");
pthread_mutex_unlock(&mutex);
//printf("recover SIGALRM\n");
return 0;
}




void* thread_set_timer(void* arg)
{
struct own_timer *timer;
struct timeval val;
val.tv_sec = 0;
val.tv_usec = 500000;
set_timer(&timer, val, timeout4, NULL);
}

int main (int argc, char* argv[])
{
struct timeval val;
signal(SIGALRM,sig_alarm);

int i = 0;
for (i = 1;i <= 500; i++) {

val.tv_sec = i;
val.tv_usec = 0;
set_timer(&t1, val, timeout, i);
}


//pthread_t tid;
//pthread_create(&tid,NULL,thread_set_timer,NULL);
while(1) {
#if 0
printf("test = %d\n",test);
if(test == 10) {
free_timer(t2);
free_timer(t3);
}
if(test == 15) {
free_timer(t1);
set_timer(&t2, val, timeout2, NULL);
set_timer(&t3, val, timeout3, NULL);
}

#endif
sleep(1);
}
return 0;
}

运行结果

timeout 1 >>
timeout 2 >>
timeout 3 >>
timeout 1 >>
timeout 4 >>
timeout 1 >>
timeout 2 >>
timeout 5 >>

后续考虑优化

1 不使用信号

setitimer利用SIGALARM信号,期间不能使用sleep()等系统调用,用相同功能的函数代替之,比如用sleep+thread实现一个setitimer,自己实现回调。
要求严格计时,在主线程里的主回调相当与sig_alarm.这个线程的优先级应该最高保证每次计时完成时,都能及时调用主回调。

2 数据结构

双向链表在大量定时器申请时,并无优势,可换成最小堆。

3 原理改进

考虑时间轮 并且tick分级。比如 某次定时需要5555个tick 每轮1000 需要 5*1000 +5*100 + 5*10 +5个tick。在不同时间轮上,计数不同片,最后等待轮最大的完成即可。