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

原理

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。在不同时间轮上,计数不同片,最后等待轮最大的完成即可。

你可能感兴趣的:(Linux)