linux下多定時器的實現(經典)

在南京看海發表於2016-07-26

一、已有的定時器介面
   時空管理是計算機系統的主要任務。在時間管理中,我們經常利用定時器處理事情:比如tcp協議中利用定時器管理包超時,視訊顯示中利用定時器來定時顯示視訊幀,web服務中利用定時器來管理使用者的超時。windows系統提供了SetTimer和timeSetEvent等定時器介面,linux中則提供了setitimer等介面。這些函式的介面很類似,大體上都是使用者提供回撥函式和超時時間向OS註冊一個定時器事件,OS在超時時間到了的時候,呼叫使用者提供的回撥函式來完成使用者想要做的事情。windows下的介面支援單程式中擁有多個定時器,而linux則只允許單程式擁有一個定時器,因此在linux下的單程式中要使用多個定時器,則需要自己維護管理,這是本文寫作的出發點。另外,OS提供的定時器管理演算法在大規模定時器的管理方面可能還不盡人意,這時候就需要使用者去優化管理演算法了,本文在這方面提供了一點素材。

二、一個最簡單的多定時器的實現(linux版)
1、實現細節 
  這個實現允許使用者使用多個自定義的定時器,每個自定義的定時器將週期地被觸發直到其被刪除。實現的主要思路是:
    i)首先在初始化多定時器(init_mul_timer)時利用setitimer註冊一個基本的時間單位(如1s)的定時事件;
    ii)使用者需要set_a_timer註冊
自定義定時器時,在timer_manage管理結構中記錄這個定時器的回撥函式和定時週期等引數;
    iii)當基本的時間單位到期後(如SIGALRM訊號到達時),遍歷整個timer_manage,如果有自定義定時器的超時時間到了,就執行相應的回撥函式,並將
自定義定時器的超時時間置為最初值;否則將自定義定時器的超時時間相應地減一個基本的時間單位;
    iv)使用者通過del_a_timer來刪除某個定時器,通 過destroy_mul_timer來刪除整個多定時器。
2、程式碼
i) mul_timer.h

 

/* This file provides an interface of multiple timers. for convenience, it simplify signal processing.
 * Also, it can't be used in multithreading environment.
 * Author:bripengandre
 * Date:2009-04-29
 */

#ifndef _MUL_TIMER_H_
#define _MUL_TIMER_H_

#include <sys/time.h>

#define MAX_TIMER_CNT 10
#define MUL_TIMER_RESET_SEC 10
#define TIMER_UNIT 60
#define MAX_FUNC_ARG_LEN 100
#define INVALID_TIMER_HANDLE (-1)

typedef int timer_handle_t;

typedef struct _timer_manage
{
    struct _timer_info
    {
        int state; /* on or off */
        int interval;
        int elapse; /* 0~interval */
        int (* timer_proc) (void *arg, int arg_len);
        char func_arg[MAX_FUNC_ARG_LEN];
        int arg_len;
    }timer_info[MAX_TIMER_CNT];

    void (* old_sigfunc)(int);
    void (* new_sigfunc)(int);
    struct itimerval value, ovalue;
}_timer_manage_t;

/* success, return 0; failed, return -1 */
int init_mul_timer(void);
/* success, return 0; failed, return -1 */
int destroy_mul_timer(void);
/* success, return timer handle(>=0); failed, return -1 */
timer_handle_t set_a_timer(int interval, int (* timer_proc) (void *arg, int arg_len), void*arg, int arg_len);
/* success, return 0; failed, return -1 */
int del_a_timer(timer_handle_t handle);

#endif /* _MUL_TIMER_H_ */

ii)mul_timer.c

 

 

 

#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <time.h> 
#include "mul_timer.h"

static struct _timer_manage timer_manage;

static void sig_func(int signo);

/* success, return 0; failed, return -1 */
int init_mul_timer(void)
{
    int ret;
    
    memset(&timer_manage, 0, sizeof(struct _timer_manage));
    if( (timer_manage.old_sigfunc = signal(SIGALRM, sig_func)) == SIG_ERR)
    {
        return (-1);
    }
    timer_manage.new_sigfunc = sig_func;
    
    timer_manage.value.it_value.tv_sec = MUL_TIMER_RESET_SEC;
    timer_manage.value.it_value.tv_usec = 0;
    timer_manage.value.it_interval.tv_sec = TIMER_UNIT;
    timer_manage.value.it_interval.tv_usec = 0;
    ret = setitimer(ITIMER_REAL, &timer_manage.value, &timer_manage.ovalue); 
    
    return (ret);
}


/* success, return 0; failed, return -1 */
int destroy_mul_timer(void)
{
    int ret;
    
    if( (signal(SIGALRM, timer_manage.old_sigfunc)) == SIG_ERR)
    {
        return (-1);


    }

    ret = setitimer(ITIMER_REAL, &timer_manage.ovalue, &timer_manage.value);
    if(ret < 0)
    {
        return (-1);
    } 
    memset(&timer_manage, 0, sizeof(struct _timer_manage));
    
    return(0);
}


/* success, return timer handle(>=0); failed, return -1 */
timer_handle_t set_a_timer(int interval, int (* timer_proc) (void *arg, int arg_len), void *arg,int arg_len)
{
    int i;
    
    if(timer_proc == NULL || interval <= 0)
    {
        return (-1);
    } 
    
    for(= 0; i < MAX_TIMER_CNT; i++)
    {
        if(timer_manage.timer_info[i].state == 1)
        {
            continue;
        }
        
        memset(&timer_manage.timer_info[i], 0, sizeof(timer_manage.timer_info[i]));
        timer_manage.timer_info[i].timer_proc = timer_proc;
        if(arg != NULL)
        {
            if(arg_len > MAX_FUNC_ARG_LEN)
            {
                return (-1);
            }
            memcpy(timer_manage.timer_info[i].func_arg, arg, arg_len);
            timer_manage.timer_info[i].arg_len = arg_len;
        }
        timer_manage.timer_info[i].interval = interval;
        timer_manage.timer_info[i].elapse = 0;
        timer_manage.timer_info[i].state = 1;
        break;
    }
    
    if(>= MAX_TIMER_CNT)
    {
        return (-1);
    }
    return (i);
}


/* success, return 0; failed, return -1 */
int del_a_timer(timer_handle_t handle)
{
    if(handle < 0 || handle >= MAX_TIMER_CNT)
    {
        return (-1);
    }
    
    memset(&timer_manage.timer_info[handle], 0, sizeof(timer_manage.timer_info[handle]));
    
    return (0);
}


static void sig_func(int signo)
{
    int i;
    for(= 0; i < MAX_TIMER_CNT; i++)
    {
        if(timer_manage.timer_info[i].state == 0)
        {
            continue;
        }
        timer_manage.timer_info[i].elapse++;
        if(timer_manage.timer_info[i].elapse == timer_manage.timer_info[i].interval)
        {
            timer_manage.timer_info[i].elapse = 0;
            timer_manage.timer_info[i].timer_proc(timer_manage.timer_info[i].func_arg,timer_manage.timer_info[i].arg_len);
        }
    }
}


#define _MUL_TIMER_MAIN


#ifdef _MUL_TIMER_MAIN

static void get_format_time(char *tstr)
{
    time_t t;
    
    t = time(NULL);
    strcpy(tstr, ctime(&t));
    tstr[strlen(tstr)-1] = '/0';
    
    return;
}


timer_handle_t hdl[3], call_cnt = 0;
int timer_proc1(void *arg, int len)
{
    char tstr[200];
    static int i, ret;
    
    get_format_time(tstr);
    printf("hello %s: timer_proc1 is here./n", tstr);
    if(>= 5)
    {
        get_format_time(tstr);
        ret = del_a_timer(hdl[0]);
        printf("timer_proc1: %s del_a_timer::ret=%d/n", tstr, ret);
    }
    i++;
    call_cnt++;
    
    
    return (1);
}

int timer_proc2(void * arg, int len)
{
    char tstr[200];
    static int i, ret;
    
    get_format_time(tstr);
    printf("hello %s: timer_proc2 is here./n", tstr);
    if(>= 5)
    {
        get_format_time(tstr);
        ret = del_a_timer(hdl[2]);
        printf("timer_proc2: %s del_a_timer::ret=%d/n", tstr, ret);
    }
    i++;
    call_cnt++;
    
    return (1);
}


int main(void)
{
    char arg[50];
    char tstr[200];
    int ret;
    
    init_mul_timer();
    hdl[0] = set_a_timer(2, timer_proc1, NULL, 0);
    printf("hdl[0]=%d/n", hdl[0]);
    hdl[1] = set_a_timer(3, timer_proc2, arg, 50);
    printf("hdl[1]=%d/n", hdl[1]);
    hdl[2] = set_a_timer(3, timer_proc2, arg, 101);
    printf("hdl[1]=%d/n", hdl[2]);
    while(1)
    {
        if(call_cnt >= 12)
        {
            get_format_time(tstr);
            ret = destroy_mul_timer();
            printf("main: %s destroy_mul_timer, ret=%d/n", tstr, ret);
            call_cnt++;
        }
        if(call_cnt >= 20)
        {
            break;
        }
    }
    
    return 0;
}

#endif

3、缺陷
   i)新建定時器、遍歷定時器和刪除定時器(查詢哪個定時器超時)時時間複雜度都為O(n)(n是定時器的個數);
   ii)適用環境是單執行緒環境,如要用於多執行緒,需新增同步操作。
   iii)程式中有些小bug,如對新建超時時間為0的定時器沒有妥善的處理。

三、多定時器的改進版
1、思路
   改進定時器的實現,即是改善二種所指出的幾個缺陷,如下是一個改進版,主要是將遍歷超時時間的時間複雜度降為了O(1).
   改善思路:各定時器以一個連結串列的形式組織起來,除連結串列頭定時器的超時時間是用絕對時間紀錄的外,其它定時器的超時時間均用相對時間(即超時時間-前一個定時器的超時時間)紀錄.
   注意,各定時器都是一次性的,當定時器的超時被處理後,定時器將被自動刪除.另外如果將定時器的結點改為雙向結構,可以將刪除定時器的時間複雜度降為O(1).
2、資料結構
   每個定時器都有一個唯一的ID,這個ID是如下的結構體:

 

 

 

 

typedef struct _timer_handle
{
        unsigned long ptr;
        unsigned long entry_id;
}*timer_handle_t;

   ptr紀錄的是定時器結點的地址,entry_id則是一個自多定時器初始化後自增的id.ptr和entry_id一起組成定時器結點的key,一方面使得新建定時器時生成key的過程大為簡化,另一方面使得刪除定時器的時間複雜度降為O(1)(前提是定時器結點採用雙向結構)。
   定時器結點的資料結構如下:

 

 

 

 

/* timer entry */
typedef struct _mul_timer_entry
{
    char is_use; /* 0, not; 1, yes */
    struct _timer_handle handle;
    unsigned int timeout;
    unsigned int elapse; /* */
    int (* timer_proc) (void *arg, unsigned int *arg_len); /* callback function */
    void *arg;
    unsigned int *arg_len;
    struct _mul_timer_entry *etr_next;
}mul_timer_entry_t;

其中的is_use是用來防止這樣一種情況:使用者在回撥函式中呼叫kill_timer來刪除定時器,這個時候kill_timer和遍歷定時器中都有刪除結點的操作,有可能將整個連結串列搞混亂。所以在呼叫使用者的回撥函式前先將is_use置1,在kill_timer中需檢查is_use,只有在 is_use為0的情況下,才執行清理定時器結點的操作。
3、程式碼(windows版)
i)mul_timer.h

 

 

 

 

/* This file provides an interface of multiple timers. it can't be used in multithreading environment.
 * Author:bripengandre
 * Date:2009-07-19
 */


#ifndef _MUL_TIMER_H_
#define _MUL_TIMER_H_
#include <windows.h>

typedef struct _timer_handle
{
        unsigned long ptr;
        unsigned long entry_id;
}*timer_handle_t;

/* timer entry */
typedef struct _mul_timer_entry
{
    char is_use; /* 0, not; 1, yes */
    struct _timer_handle handle;
    unsigned int timeout;
    unsigned int elapse; /* */
    int (* timer_proc) (void *arg, unsigned int *arg_len); /* callback function */
    void *arg;
    unsigned int *arg_len;
    struct _mul_timer_entry *etr_next;
}mul_timer_entry_t;

typedef struct _mul_timer_manage
{
    unsigned long entry_id;
    unsigned int timer_cnt;
    unsigned int time_unit;
    struct _mul_timer_entry *etr_head;
    UINT timer_id;
};



struct _mul_timer_manage *init_mul_timer(unsigned int time_unit);
timer_handle_t set_timer(struct _mul_timer_manage *ptimer, unsigned int time_out, int (*timer_proc) (void *arg, unsigned int *arg_len), void *arg, unsigned int *arg_len);
int kill_timer(struct _mul_timer_manage *ptimer, timer_handle_t hdl);
int get_timeout_byhdl(struct _mul_timer_manage *ptimer, timer_handle_t hdl);
int get_timeout_bytimeproc(struct _mul_timer_manage *ptimer, int (* timer_proc) (void*arg, unsigned int *arg_len));
int release_mul_timer(struct _mul_timer_manage *ptimer);

int is_valid_time_hdl(timer_handle_t hdl);

#endif /* _MUL_TIMER_H_ */

ii)mul_timer.c

 

 

 

 

#include "mul_timer.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>


void CALLBACK traverse_mul_timer(UINT uTimerID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2);
static int print_mul_timer(struct _mul_timer_manage *ptimer);

struct _mul_timer_manage *init_mul_timer(unsigned int time_unit)
{
    struct _mul_timer_manage *p;
    
    if( (= malloc(sizeof(struct _mul_timer_manage))) == NULL)
    {
        return (NULL);
    }
    
    p->etr_head = NULL;
    p->timer_cnt = 0;
    p->time_unit = time_unit;
    p->entry_id = 0;
    
    p->timer_id = timeSetEvent(time_unit, 0, (LPTIMECALLBACK )traverse_mul_timer,(DWORD)p, TIME_PERIODIC);
    
    return(p);
}


timer_handle_t set_timer(struct _mul_timer_manage *ptimer, unsigned int time_out, int (*timer_proc) (void *arg, unsigned int *arg_len), void *arg, unsigned int *arg_len)
{
    struct _mul_timer_entry *p, *prev, *pnew;
    
    if(ptimer == NULL || time_out == 0)
    {
        return (NULL);
    }
    
    
    if( (pnew = malloc(sizeof(struct _mul_timer_entry))) == NULL)
    {
        return (NULL);
    }
    pnew->is_use = 0;
    pnew->arg = arg;
    pnew->arg_len = arg_len;
    pnew->elapse = 0;
    pnew->timer_proc = timer_proc;
    
    p = ptimer->etr_head;
    prev = NULL;
    while(!= NULL)
    {
        if(p->timeout < time_out) /* assume the latest time_proc has higher priority */
        {
            time_out = time_out-p->timeout;
            prev = p;
            p = p->etr_next;
        }
        else
        {
            p->timeout -= time_out;
            break;
        }
    }
    
    pnew->timeout = time_out;
    pnew->etr_next = p;
    pnew->handle.ptr = (unsigned long )pnew;
    pnew->handle.entry_id = ptimer->entry_id;
    ptimer->entry_id++;

    if(prev == NULL)
    {
        ptimer->etr_head = pnew;
    }
    else
    {
        prev->etr_next = pnew;
    }    
    ptimer->timer_cnt++;
    
    return (&pnew->handle);
}


int kill_timer(struct _mul_timer_manage *ptimer, timer_handle_t hdl)
{
    struct _mul_timer_entry *p, *prev;
    
    if(ptimer == NULL)
    {
        return (0);
    }
    
    
    p = ptimer->etr_head;
    prev = NULL;
    while(!= NULL)
    {
        if(p->handle.ptr == hdl->ptr && p->handle.entry_id == hdl->entry_id)
        {
            break;
        }

        prev = p;
        p = p->etr_next;
    }
    
    /* no such timer or timer is in use, return 0 */
    if(== NULL || (!= NULL && p->is_use == 1)) 
    {
        return (0); 
    }
    
    /* has found the timer */
    if(prev == NULL)
    {
        ptimer->etr_head = p->etr_next;
    }
    else
    {
        prev->etr_next = p->etr_next;
    }
    
    /* revise timeout */
    if(p->etr_next != NULL)
    {
        p->etr_next->timeout += p->timeout;
    }
    
    /* delete the timer */
    free(p);
    p = NULL;
    ptimer->timer_cnt--;
    
    return (1);
}


int get_timeout_byhdl(struct _mul_timer_manage *ptimer, timer_handle_t hdl)
{
    struct _mul_timer_entry *p;
    unsigned int timeout;
    
    if(ptimer == NULL || (struct _mul_timer_entry *)(hdl) == NULL)
    {
        return (-1);
    }
    
    
    timeout = 0;
    p = ptimer->etr_head;
    while(!= NULL)
    {
     if(p->handle.ptr == hdl->ptr && p->handle.entry_id == hdl->entry_id)
        {
            break;
        }

        timeout += p->timeout;
        p = p->etr_next;
    }
    
    if(== NULL)
    {
        return (-1);
    }
    else
    {
        return ((int)timeout+p->timeout);
    }    
    
}


int get_timeout_bytimeproc(struct _mul_timer_manage *ptimer, int (* timer_proc) (void*arg, unsigned int *arg_len))
{
    struct _mul_timer_entry *p;
    unsigned int timeout;
    
    if(ptimer == NULL || timer_proc == NULL)
    {
        return (-1);
    }
    
    p = ptimer->etr_head;
    while((!= NULL) && (p->timer_proc != timer_proc))
    {
        timeout += p->timeout;
        p = p->etr_next;
    }
    
    if(== NULL)
    {
        return (-1);
    }
    else
    {
        return (timeout+p->timeout);
    }    
}


int release_mul_timer(struct _mul_timer_manage *ptimer)
{
    struct _mul_timer_entry *p, *ptmp;
    
    if(ptimer == NULL)
    {
        return (0);
    }
    
    timeKillEvent(ptimer->timer_id);
    /* delete all timers */
    p = ptimer->etr_head;
    while(!= NULL)
    {
        ptmp = p;
        p = p->etr_next;
        free(ptmp);
    }
    /* delete timer_manage */
    free(ptimer);
    ptimer = NULL;
    
    return (1);
}

int is_valid_time_hdl(timer_handle_t hdl)
{
    if(hdl == NULL)
    {
        return (0);
    }
    else
    {
        return (1);
    }    
}


void CALLBACK traverse_mul_timer(UINT uTimerID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2)
{
    struct _mul_timer_manage *ptimer;
    struct _mul_timer_entry *p, *ptmp;
    unsigned int timeout;
    
    ptimer = (struct _mul_timer_manage *)dwUser;
    if(ptimer == NULL)
    {
        return;
    }
    
    timeout = ptimer->time_unit;
    p = ptimer->etr_head;
    while(!= NULL)
    {
        if(p->timeout <= timeout)
        {
            p->is_use = 1;
            p->timer_proc(p->arg, p->arg_len);
            ptmp = p;
            timeout -= p->timeout;
            p = p->etr_next;
            free(ptmp);
            ptimer->etr_head = p;
        }
        else
        {
            p->timeout -= timeout;
            p->elapse += timeout;
            ptimer->etr_head = p;
            break;
        }
    }
    if(== NULL)
    {
        ptimer->etr_head = NULL;
    }    
    
    return; 
}


static int print_mul_timer(struct _mul_timer_manage *ptimer)
{
    struct _mul_timer_entry *p;
    int i;
    
    if(ptimer == NULL)
    {
        return (0);
    }
    
    printf("***************************mul_timer statistics start************************/n");
    printf("this mul_timer's time_unit=%u, etr_head=%p and has %d timers:/n", ptimer->time_unit, ptimer->etr_head, ptimer->timer_cnt);
    
    p = ptimer->etr_head;
    i = 0;
    while(!= NULL)
    {
        printf("the %d timer: timeout=%u, elapse=%u, timer_proc=%p, arg=%p, arg_len=%p, etr_next=%p/n"
            , i+1, p->timeout, p->elapse, p->timer_proc, p->arg, p->arg_len,p->etr_next);
        p = p->etr_next;
        i++;
    }
    printf("***************************mul_timer statistics end************************/n");
    
    return (1);
}


#define _MUL_TIMER_MAIN

#ifdef _MUL_TIMER_MAIN

static void get_format_time(char *tstr)
{
    time_t t;
    
    t = time(NULL);
    strcpy(tstr, ctime(&t));
    tstr[strlen(tstr)-1] = '/0';
    
    return;
}

timer_handle_t hdl[100];
int call_cnt = 0;
struct _mul_timer_manage *ptimer;

int timer_proc1(void *arg, unsigned int *len)
{
    char tstr[200];
    static int i, ret;
    
    get_format_time(tstr);
    printf("call_cnt=%d, hello %s: timer_proc1 is here./n", call_cnt, tstr);
    i++;
    call_cnt++;
    
    
    return (1);
}

int timer_proc2(void * arg, unsigned int *len)
{
    char tstr[200];
    static int i, ret;
    
    get_format_time(tstr);
    printf("call_cnt=%d, hello %s: timer_proc2 is here: arg = %s, len = %d./n",call_cnt, tstr, arg, *len);
    i++;
    call_cnt++;
    
    return (1);
}


int main(void)
{
    char arg[50] = "hello, multiple timers";
    char tstr[200];
    int ret;
    int len = 50, i;
    
    ptimer = init_mul_timer(1000);
    
    for(= 0; i < 10; i++)
    {
        hdl[i<<1] = set_timer(ptimer, 1000*(i+1), timer_proc1, NULL, NULL);
        printf("hdl[0i<<1=%d, is_valid_hdl=%d/n", hdl[i<<1],is_valid_time_hdl(hdl[i<<1]));
        hdl[(i<<1)+1] = set_timer(ptimer, 3000*(i+1), timer_proc2, arg, &len);
        printf("hdl[i<<1+1]=%d, is_valid_hdl=%d/n", hdl[(i<<1)+1],is_valid_time_hdl(hdl[(i<<1)+1]));
        print_mul_timer(ptimer);
    }

    ret = kill_timer(ptimer, hdl[17]);
    printf("ret=kill_timer=%d/n", ret);
    print_mul_timer(ptimer);    
    printf("hd[19]->timout=%d/n", get_timeout_byhdl(ptimer, hdl[19]));

    while(1)
    {
        if(call_cnt == 15)
        {
            get_format_time(tstr);
            ret = release_mul_timer(ptimer);
            printf("call_cnt=%d, main: %s destroy_mul_timer, ret=%d/n",call_cnt, tstr, ret);
            call_cnt++;
        }
    }
    
    return 0;
}

#endif

3、缺陷
i)新建定時器的時間複雜度為O(n),刪除定時器的時間複雜度也為O(n)(簡單地將定時器結點改為雙向結構,可將複雜度降為O(1));
ii)不能用於多執行緒環境
四 、多定時器的工業級實現
 1、time wheelz演算法
   以前的BSD核心以及現在的linux核心的實現與三中所用演算法相似(未實證,只是據說),據說現在的BSD核心已採用了較好的time wheelz演算法。
   time wheez演算法的優點:
   i)將新建定時器的時間複雜度降近似為O(1)。它根據定時器的超時值,將新定時器雜湊到hash桶中;
   ii)遍歷檢查定時器的時間複雜度也近似為O(桶大小),如果雜湊均勻。
   iii)刪除定時器的時間複雜度近似為O(1),通過hash演算法或臨時儲存(空間換時間的演算法)。
2、time wheelz的實現

 

 

 

   請參考文末給出的兩個論文,慚愧得很,文章我也只是稍微瞄了下,以後有用得著的時候再深究吧。

 

 

轉自 http://blog.csdn.net/zhangxinrun/article/details/5914191

相關文章