1. 程式人生 > >技術系列之 定時器(一)

技術系列之 定時器(一)

一、 基礎知識
1、時間型別。
Linux下常用的時間型別有4個:time_t,struct timeval,struct timespec,struct tm。
(1)time_t是一個長整型,一般用來表示用1970年以來的秒數。
(2)Struct timeval有兩個成員,一個是秒,一個是微妙。

struct timeval {
               
long tv_sec;        /* seconds */
               
long tv_usec;  /* microseconds */
       }
;

(3)struct timespec有兩個成員,一個是秒,一個是納秒。

struct timespec{
                      time_t  tv_sec;         
/* seconds */
                      
long    tv_nsec;        /* nanoseconds */
              }
;

(4)struct tm是直觀意義上的時間表示方法:

struct tm {
                      
int     tm_sec;         /* seconds */
                      
int     tm_min;         
/* minutes */
                      
int     tm_hour;        /* hours */
                      
int     tm_mday;        /* day of the month */
                      
int     tm_mon;         /* month */
                      
int     tm_year;        /* year */
                      
int     tm_wday;        /* day of the week 
*/

                      
int     tm_yday;        /* day in the year */
                      
int     tm_isdst;       /* daylight saving time */
              }
;

2、 時間操作
(1) 時間格式間的轉換函式
主要是 time_t、struct tm、時間的字串格式之間的轉換。看下面的函式引數型別以及返回值型別:

char*asctime(const struct tm *tm);
char*ctime(const time_t *timep);
struct tm 
*gmtime(const time_t *timep);
struct tm 
*localtime(const time_t *timep);
time_t mktime(struct tm 
*tm);

gmtime和localtime的引數以及返回值型別相同,區別是前者返回的格林威治標準時間,後者是當地時間。
(2) 獲取時間函式
兩個函式,獲取的時間型別看原型就知道了:

time_t time(time_t *t);
int gettimeofday(struct timeval *tv, struct timezone *tz);

前者獲取time_t型別,後者獲取struct timeval型別,因為型別的緣故,前者只能精確到秒,後者可以精確到微秒。
二、 延遲函式
主要的延遲函式有:sleep(),usleep(),nanosleep(),select(),pselect().

unsigned int sleep(unsigned int seconds);
void usleep(unsigned long usec);
int nanosleep(const struct timespec *req, struct timespec *rem);
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,struct timeval *timeout);
int pselect(int   n,   fd_set   *readfds,  fd_set  *writefds,  fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask);

alarm函式是訊號方式的延遲,這種方式不直觀,這裡不說了。
僅通過函式原型中時間引數型別,可以猜測sleep可以精確到秒級,usleep/select可以精確到微妙級,nanosleep和pselect可以精確到納秒級。
而實際實現中,linux上的nanosleep和alarm相同,都是基於核心時鐘機制實現,受linux核心時鐘實現的影響,並不能達到納秒級的精度,man nanosleep也可以看到這個說明,man裡給出的精度是:Linux/i386上是10 ms ,Linux/Alpha上是1ms。
這裡有一篇文章http://blog.csdn.net/zhoujunyi/archive/2007/03/30/1546330.aspx,測試了不同延遲函式之間的精確度。文章給出的結論是linux上精度最高的是select,10ms級別。我在本機器測試select和pselect相當,都達到了1ms級的精度,精度高於文章中給出的10ms,sleep在秒級以上和usleep/nanosleep相當。下面貼下我機器上1ms時候的測試結果,其他不貼了:

sleep           10000-1000 
usleep           
100029741974 
nanosleep        
100029901990 
select           
1000991-9 
pselect           
1000990-10 
gettimeofday           
100010000

而使用gettimeofday迴圈不停檢測時間,可精確微秒級,不過不適宜用來做定時器模組。
因此後面的定時期模組將選擇select為延遲函式。
三、 定時器模組需求以及實現概述
1、需求。從實現結果的角度說來,需求就是最終的使用方式。呵呵,不詳細描述需求了,先直接給出我實現的CTimer類的三個主要介面:

Class CTimer{
Public:
CTimer(unsigned 
int vinterval,void (*vfunc)(CTimer *,void*),void*vdata,TimerType vtype);
void start();
void stop();
void reset(unsigned int vinterval);
}
;

使用定時器模組的步驟如下:
(1) 例項化一個CTimer,引數的含義依次是:vinterval間隔時間(單位ms),vfunc是時間到回撥的函式,vdata回撥函式使用的引數,vtype定時器的型別,分一次型和迴圈型兩種。
(2) 呼叫start方法。
(3) 必要的時候呼叫stop和reset。
2、實現。簡單描述下定時器模組的實現,有一個manager單例類儲存所有CTimer物件,開啟一執行緒執行延遲函式,每次延遲間隔到,掃描儲存CTimer的容器,對每個CTimer物件執行減少時間操作,減少到0則執行回撥函式。對一次性CTimer,超時則從容器中刪除,迴圈型的將間隔時間重置,不從容器中移除。
CTimer的start執行將物件插入到manager容器中操作;stop執行將物件從manager容器中刪除的操作;reset執行先刪除,重置間隔,然後再放到容器中,reset不改變CTimer的定時器型別屬性。
四、 定時器模組的資料結構選擇
Manager類的容器要頻繁進行的操作涉及插入、刪除、查詢等。
誤區:(1)簡單看,好象該容器要是有序的,方便插入刪除等,貌似紅黑樹比較合適。其實不然,插入刪除操作的頻率很低,最頻繁的還是每次時延到,對容器的掃描並做時間減少操作,紅黑樹在做順序掃描相對連結串列並沒什麼優勢。
(2) 插入的時候依照順序連結串列的方式插入到合適的位置保持排序,以保證超時的物件都在連結串列的頭端。其實這也是沒必要的,每次時延到,對每一個物件都要做時間減少操作,因此不管是有序還是無序,都是一次掃描就執行完下面操作:減少時間、判斷是否超時,是則執行回撥,繼續判斷是什麼型別,一次型的則執行完就移除,迴圈型則執行完直接重置間隔就可。
因此,只需要能快速插入頭、刪除結點、遍歷就好。我的實現直接使用BSD核心中的資料結構LIST,插入頭、刪除時間複雜度都是1,遍歷就不說了。linux下/usr/include/sys下有標頭檔案queue.h裡也有LIST結構以及操作的定義。貌似linux下的少了遍歷巨集:

#define LIST_FOREACH(var, head, field)     /
 
for((var) = LIST_FIRST(head);     /
     (var)
!= LIST_END(head);     /
     (var) 
= LIST_NEXT(var, field))

五、 詳細實現
這裡帖出主要的程式碼,請重點關注CTimerManager:: process方法,不再詳細說了。需要詳細的全部程式碼,可來信索取,整體程式碼很簡單,就兩個類。

class CTimer
{
friend 
class CTimerManager;
public:
    typedef 
enum
    
{
        TIMER_IDLE
=0,  //start前以及手動呼叫stop後的狀態
        TIMER_ALIVE,  //在manager的list裡時候的狀態
        TIMER_TIMEOUT  //超時後被移除的狀態,迴圈型的沒有
    }
TimerState;
    typedef 
enum
    
{
        TIMER_ONCE
=0,  //一次型
        TIMER_CIRCLE   //迴圈型
    }
TimerType;
    CTimer(unsigned 
int vinterval,void (*vfunc)(CTimer *,void*),void*vdata,TimerType vtype);
    
void start();
    
void stop();
    
void reset(unsigned int vinterval);
    
~CTimer();
private:
    unsigned 
int id_;     //測試用
    unsigned int m_interval;  //間隔,不變
    unsigned int m_counter;  //開始設定為interval,隨延遲時間到,減少
    TimerState m_state;      //狀態
    TimerType m_type;        //型別
void (*m_func)(CTimer *,void*);//回撥函式
void* m_data;  //回撥函式引數
    LIST_ENTRY(CTimer) entry_;  //LIST的使用方式
}
;
/*建構函式*/
CTimer::CTimer(unsigned 
int vinterval,void (*vfunc)(CTimer *,void*),void*vdata,TimerType vtype):

    m_interval(vinterval),m_counter(vinterval),
    m_state(TIMER_IDLE),m_type(vtype),
    m_func(vfunc),m_data(vdata)
{}
/*開始定時器*/
void CTimer::start()
{
    CTimerManager::instance()
->add_timer(this);
}

/*停止定時器*/
void CTimer::stop()
{
    CTimerManager::instance()
->remove_timer(this);
}

/*reset定時器*/
void CTimer::reset(unsigned int vinterval)
{
    CTimerManager::instance()
->remove_timer(this);
    m_counter
=m_interval=vinterval;
    CTimerManager::instance()
->add_timer(this);
}

/*解構函式,stop操作不能省略,避免delete前忘記stop*/
CTimer::
~CTimer()
{
    
if(m_state==TIMER_ALIVE)
        stop();
}

CTimerManager的:

class CTimerManager
{
public:
    
    typedef 
enum
    
{
        TIMER_MANAGER_STOP
=0,
        TIMER_MANAGER_START
    }
TimerManagerState;
    
    
static CTimerManager * instance();
    
void add_timer(CTimer * vtimer);//執行緒安全的add
void remove_timer(CTimer * vtimer);//執行緒安全的remove
void start();  //開始process執行緒
void stop();  //停止process執行緒
void dump();
protected:
    
staticvoid* process(void*); //實際的定時器時間延遲執行緒
private:    
    
void add_timer_(CTimer * vtimer);//非執行緒安全的add
void remove_timer_(CTimer * vtimer);//非執行緒安全的remove
    
    CTimerManager();
    
static pthread_mutex_t m_mutex;
    
static CTimerManager * m_instance;
    
    TimerManagerState m_state;
    LIST_HEAD(,CTimer) list_;  
//LIST使用方式

    
sta