技术系列之 定时器(一)

作者:CppExplore 网址:http://www.cppblog.com/CppExplore/
一、 基础知识
1、时间类型。
Linux下常用的时间类型有4个:time_t,struct timeval,struct timespec,struct tm。
(1)time_t是一个长整型,一般用来表示用1970年以来的秒数。
(2)Struct timeval有两个成员,一个是秒,一个是微妙。

ExpandedBlockStart.gif struct timeval  {
ExpandedSubBlockStart.gif               
long tv_sec;        /* seconds */
ExpandedSubBlockStart.gif               
long tv_usec;  /* microseconds */
ExpandedBlockEnd.gif       }
;

(3)struct timespec有两个成员,一个是秒,一个是纳秒。

ExpandedBlockStart.gif struct timespec {
ExpandedSubBlockStart.gif                      time_t  tv_sec;         
/* seconds */
ExpandedSubBlockStart.gif                      
long    tv_nsec;        /* nanoseconds */
ExpandedBlockEnd.gif              }
;

(4)struct tm是直观意义上的时间表示方法:

ExpandedBlockStart.gif struct tm  {
ExpandedSubBlockStart.gif                      
int     tm_sec;         /* seconds */
ExpandedSubBlockStart.gif                      
int     tm_min;         /* minutes */
ExpandedSubBlockStart.gif                      
int     tm_hour;        /* hours */
ExpandedSubBlockStart.gif                      
int     tm_mday;        /* day of the month */
ExpandedSubBlockStart.gif                      
int     tm_mon;         /* month */
ExpandedSubBlockStart.gif                      
int     tm_year;        /* year */
ExpandedSubBlockStart.gif                      
int     tm_wday;        /* day of the week */
ExpandedSubBlockStart.gif                      
int     tm_yday;        /* day in the year */
ExpandedSubBlockStart.gif                      
int     tm_isdst;       /* daylight saving time */
ExpandedBlockEnd.gif              }
;

2、 时间操作
(1) 时间格式间的转换函数
主要是 time_t、struct tm、时间的字符串格式之间的转换。看下面的函数参数类型以及返回值类型:

None.gif char   * asctime( const  struct tm  * tm);
None.gif
char   * ctime( const  time_t  * timep);
None.gifstruct tm 
* gmtime( const  time_t  * timep);
None.gifstruct tm 
* localtime( const  time_t  * timep);
None.giftime_t mktime(struct tm 
* tm);

gmtime和localtime的参数以及返回值类型相同,区别是前者返回的格林威治标准时间,后者是当地时间。
(2) 获取时间函数
两个函数,获取的时间类型看原型就知道了:

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

前者获取time_t类型,后者获取struct timeval类型,因为类型的缘故,前者只能精确到秒,后者可以精确到微秒。
二、 延迟函数
主要的延迟函数有:sleep(),usleep(),nanosleep(),select(),pselect().

None.gif unsigned  int  sleep(unsigned  int  seconds);
None.gif
void  usleep(unsigned  long  usec);
None.gif
int  nanosleep( const  struct timespec  * req, struct timespec  * rem);
None.gif
int  select( int  n, fd_set  * readfds, fd_set  * writefds, fd_set  * exceptfds,struct timeval  * timeout);
None.gif
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时候的测试结果,其他不贴了:

None.gif sleep            1000            0        - 1000  
None.gifusleep           
1000         2974         1974  
None.gifnanosleep        
1000         2990         1990  
None.gifselect           
1000          991           - 9  
None.gifpselect           
1000          990          - 10  
None.gifgettimeofday           
1000         1000            0

而使用gettimeofday循环不停检测时间,可精确微秒级,不过不适宜用来做定时器模块。
因此后面的定时期模块将选择select为延迟函数。
三、 定时器模块需求以及实现概述
1、需求。从实现结果的角度说来,需求就是最终的使用方式。呵呵,不详细描述需求了,先直接给出我实现的CTimer类的三个主要接口:

ExpandedBlockStart.gif Class CTimer {
InBlock.gifPublic:
InBlock.gifCTimer(unsigned 
int vinterval,void (*vfunc)(CTimer *,void *),void *vdata,TimerType vtype);
InBlock.gif
void start();
InBlock.gif
void stop();
InBlock.gif
void reset(unsigned int vinterval);
ExpandedBlockEnd.gif}
;

使用定时器模块的步骤如下:
(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下的少了遍历宏:

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

五、 详细实现
这里帖出主要的代码,请重点关注CTimerManager:: process方法,不再详细说了。需要详细的全部代码,可来信索取,整体代码很简单,就两个类。

None.gif class  CTimer
ExpandedBlockStart.gif
{
InBlock.giffriend 
class CTimerManager;
InBlock.gif
public:
InBlock.gif    typedef 
enum
ExpandedSubBlockStart.gif    
{
InBlock.gif        TIMER_IDLE
=0,  //start前以及手动调用stop后的状态
InBlock.gif
        TIMER_ALIVE,  //在manager的list里时候的状态
InBlock.gif
        TIMER_TIMEOUT  //超时后被移除的状态,循环型的没有
ExpandedSubBlockEnd.gif
    }
TimerState;
InBlock.gif    typedef 
enum
ExpandedSubBlockStart.gif    
{
InBlock.gif        TIMER_ONCE
=0,  //一次型
InBlock.gif
        TIMER_CIRCLE   //循环型
ExpandedSubBlockEnd.gif
    }
TimerType;
InBlock.gif    CTimer(unsigned 
int vinterval,void (*vfunc)(CTimer *,void *),void *vdata,TimerType vtype);
InBlock.gif    
void start();
InBlock.gif    
void stop();
InBlock.gif    
void reset(unsigned int vinterval);
InBlock.gif    
~CTimer();
InBlock.gif
private:
InBlock.gif    unsigned 
int id_;     //测试用
InBlock.gif
    unsigned int m_interval;  //间隔,不变
InBlock.gif
    unsigned int m_counter;  //开始设置为interval,随延迟时间到,减少
InBlock.gif
    TimerState m_state;      //状态
InBlock.gif
    TimerType m_type;        //类型
InBlock.gif
    void (*m_func)(CTimer *,void *);//回调函数
InBlock.gif
    void * m_data;  //回调函数参数
InBlock.gif
    LIST_ENTRY(CTimer) entry_;  //LIST的使用方式
ExpandedBlockEnd.gif
}
;
ExpandedBlockStart.gif
/*构造函数*/
None.gifCTimer::CTimer(unsigned 
int  vinterval, void  ( * vfunc)(CTimer  * , void   * ), void   * vdata,TimerType vtype):
None.gif
None.gif    m_interval(vinterval),m_counter(vinterval),
None.gif    m_state(TIMER_IDLE),m_type(vtype),
None.gif    m_func(vfunc),m_data(vdata)
ExpandedBlockStart.gif
{}
ExpandedBlockStart.gif
/*开始定时器*/
None.gif
void  CTimer::start()
ExpandedBlockStart.gif
{
InBlock.gif    CTimerManager::instance()
->add_timer(this);
ExpandedBlockEnd.gif}

ExpandedBlockStart.gif
/*停止定时器*/
None.gif
void  CTimer::stop()
ExpandedBlockStart.gif
{
InBlock.gif    CTimerManager::instance()
->remove_timer(this);
ExpandedBlockEnd.gif}

ExpandedBlockStart.gif
/*reset定时器*/
None.gif
void  CTimer::reset(unsigned  int  vinterval)
ExpandedBlockStart.gif
{
InBlock.gif    CTimerManager::instance()
->remove_timer(this);
InBlock.gif    m_counter
=m_interval=vinterval;
InBlock.gif    CTimerManager::instance()
->add_timer(this);
ExpandedBlockEnd.gif}

ExpandedBlockStart.gif
/*析构函数,stop操作不能省略,避免delete前忘记stop*/
None.gifCTimer::
~ CTimer()
ExpandedBlockStart.gif
{
InBlock.gif    
if(m_state==TIMER_ALIVE)
InBlock.gif        stop();
ExpandedBlockEnd.gif}

CTimerManager的:

None.gif class  CTimerManager
ExpandedBlockStart.gif
{
InBlock.gif
public:
InBlock.gif    
InBlock.gif    typedef 
enum
ExpandedSubBlockStart.gif    
{
InBlock.gif        TIMER_MANAGER_STOP
=0,
InBlock.gif        TIMER_MANAGER_START
ExpandedSubBlockEnd.gif    }
TimerManagerState;
InBlock.gif    
InBlock.gif    
static CTimerManager * instance();
InBlock.gif    
void add_timer(CTimer * vtimer);//线程安全的add
InBlock.gif
    void remove_timer(CTimer * vtimer);//线程安全的remove
InBlock.gif
    void start();  //开始process线程
InBlock.gif
    void stop();  //停止process线程
InBlock.gif
    void dump();
InBlock.gif
protected:
InBlock.gif    
static void * process(void *); //实际的定时器时间延迟线程
InBlock.gif
private:    
InBlock.gif    
void add_timer_(CTimer * vtimer);//非线程安全的add
InBlock.gif
    void remove_timer_(CTimer * vtimer);//非线程安全的remove
InBlock.gif
    
InBlock.gif    CTimerManager();
InBlock.gif    
static pthread_mutex_t m_mutex;
InBlock.gif    
static CTimerManager * m_instance;
InBlock.gif    
InBlock.gif    TimerManagerState m_state;
InBlock.gif    LIST_HEAD(,CTimer) list_;  
//LIST使用方式
InBlock.gif

InBlock.gif    
static unsigned int mark;  //测试,配合dump
ExpandedBlockEnd.gif
}
;
ExpandedBlockStart.gif
/*singlton的double-check实现*/
None.gifCTimerManager 
*  CTimerManager::instance()
ExpandedBlockStart.gif
{
InBlock.gif    
if(m_instance==NULL)
ExpandedSubBlockStart.gif    
{
InBlock.gif        pthread_mutex_lock(
&m_mutex);
InBlock.gif        
if(m_instance==NULL)
ExpandedSubBlockStart.gif        
{
InBlock.gif            m_instance
=new CTimerManager();
ExpandedSubBlockEnd.gif        }

InBlock.gif        pthread_mutex_unlock(
&m_mutex);
ExpandedSubBlockEnd.gif    }

InBlock.gif    
return m_instance;
ExpandedBlockEnd.gif}

ExpandedBlockStart.gif
/*process必须static,不能操作非static属性,因此传递this指针*/
None.gif
void  CTimerManager:: start()
ExpandedBlockStart.gif
{
         if(m_state==TIMER_MANAGER_STOP){
InBlock.gif    m_state
=TIMER_MANAGER_START;
InBlock.gif    pthread_t pid;
InBlock.gif    pthread_create(
&pid,0,process,this);
      }
ExpandedBlockEnd.gif}

ExpandedBlockStart.gif
/*定时器模块延迟时间线程*/
None.gif
void   *  CTimerManager:: process( void   *  arg)
ExpandedBlockStart.gif
{
InBlock.gif    pthread_detach(pthread_self());
InBlock.gif
InBlock.gif    CTimerManager 
*manage=(CTimerManager *)arg;
InBlock.gif
InBlock.gif    CTimer 
*item;
InBlock.gif    struct timeval start,end;
InBlock.gif    unsigned 
int delay;
InBlock.gif    
InBlock.gif    struct timeval tm;
InBlock.gif    gettimeofday(
&end,0);
ExpandedSubBlockStart.gif
/*使用状态控制线程运行,进而容易实现stop,也可以使用pthread_cancel粗暴的停止,需要考虑暂停点等问题*/
InBlock.gif    
while(manage->m_state==TIMER_MANAGER_START)
ExpandedSubBlockStart.gif    
{
InBlock.gif        tm.tv_sec
=0;
InBlock.gif        tm.tv_usec
=DEFULT_INTERVAL*1000;
InBlock.gif        start.tv_sec
=end.tv_sec;
InBlock.gif        start.tv_usec
=end.tv_usec;
ExpandedSubBlockStart.gif
/*不同系统的延迟函数精度不同,如果需要替换为其他延迟函数,这附近修改下就好*/
InBlock.gif        
while(select(0,0,0,0,&tm)<0&&errno==EINTR);
InBlock.gif        gettimeofday(
&end,0);
InBlock.gif
InBlock.gif        delay
=(end.tv_sec-start.tv_sec)*1000+(end.tv_usec-start.tv_usec)/1000;
InBlock.gif        pthread_mutex_lock(
&manage->m_mutex);
InBlock.gif
InBlock.gif        LIST_FOREACH(item, 
&(manage->list_), entry_)
ExpandedSubBlockStart.gif        
{
InBlock.gif            item
->m_counter<delay?item->m_counter=0:item->m_counter-=delay;
InBlock.gif            
if(item->m_counter==0)
ExpandedSubBlockStart.gif            
{
InBlock.gif                
if(item->m_func)
InBlock.gif                item
->m_func(item,item->m_data);
InBlock.gif
InBlock.gif                
if(item->m_type==CTimer::TIMER_ONCE)
ExpandedSubBlockStart.gif                
{
ExpandedSubBlockStart.gif
/*一次型的,超时,移除,并状态CTimer::TIMER_TIMEOUT*/
InBlock.gif                    manage
->remove_timer_(item);
InBlock.gif                    item
->m_state=CTimer::TIMER_TIMEOUT;
ExpandedSubBlockEnd.gif                }

InBlock.gif                
else if(item->m_type==CTimer::TIMER_CIRCLE)
ExpandedSubBlockStart.gif                
{
ExpandedSubBlockStart.gif
/*循环型的,重置counter就好*/
InBlock.gif                    item
->m_counter=item->m_interval;
ExpandedSubBlockEnd.gif                }

ExpandedSubBlockEnd.gif            }

ExpandedSubBlockEnd.gif        }

InBlock.gif
InBlock.gif        pthread_mutex_unlock(
&manage->m_mutex);
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}



六、 讨论
(1)精度问题。精度高,实时性高,但要求select等待的时间缩短,进而增加对LIST结构的扫描操作。精度低,实时性差,但会增加定时器线程的睡眠时间,减少对cpu的占用。一般的应用系统,应该尽量降低精度,避免不必要的扫描,对具体系统可考察所用到的所有定时器的实际间隔,在允许的情况下,尽量降低精度,可通过修改代码中的宏实现。为了降低定时器线程对cpu的占有时间,甚有更为粗犷型的定时器模块实现为将延迟时间取list中最小的那个间隔,保证每次延迟时间到都有回调。
(2)加锁区域问题。本文中的定时器模块实现,将定时器对象的时间减少以及函数回调的执行等再同一个临界区内执行,而有的定时器模块实现是在加锁区域执行“时间减少”操作,将减少到0的对象放到另一个超时链表中,解锁后再单独扫描超时链表执行回调操作。很明显,后者缩短了加锁时间,能及时响应其他的线程的定时器对象的start以及stop操作。但是后者对定时器操作的时序性有误差,直观反应就是可能在定时器执行了stop操作以后,仍然会有超时回调发生,特别是回调参数是指针的情况,可能引起难以发现的bug,增加调试困难。在衡量两者的利弊后,本文采用延长加锁时间以保证操作的时序性。因此,在实际的使用,回调函数应尽快返回,另一方面,尽量减少系统内使用的定时器数目,这个主要原因是延迟时间到要扫描LIST,哪种方式都避免不了。

七、使用示例

None.gif#include "timer_manager.h"
None.gif#include 
<stdio.h>
None.gif#include 
<unistd.h>
None.gif#include 
<stdlib.h>
None.gif
void func(CTimer * timer, void * data)
ExpandedBlockStart.gif
{
InBlock.gif    printf(
"hi,%d/n",(int
)(data));
ExpandedBlockEnd.gif}

ExpandedBlockStart.gif
/*随便写的,凑合着看吧。没有CTimerManager::instance()->stop();也没new对象。定时器对象可多次start和stop,使用上对暴露的接口没有任何的契约式限制,可随意调用*/
None.gif
int  main()
ExpandedBlockStart.gif
{
InBlock.gif    CTimerManager::instance()
->
start();
InBlock.gif    CTimer a(
1000,func,(void *)1
,CTimer::TIMER_CIRCLE);
InBlock.gif    CTimer a1(
2000,func,(void *)11
,CTimer::TIMER_ONCE);
InBlock.gif    CTimer a2(
3000,func,(void *)12
,CTimer::TIMER_ONCE);
InBlock.gif    CTimer a3(
1000,func,(void *)13
,CTimer::TIMER_ONCE);
InBlock.gif    
InBlock.gif    a.start();
InBlock.gif    a1.start();
InBlock.gif    a2.start();
InBlock.gif    a3.start();
InBlock.gif    a.start();
InBlock.gif    a1.start();
InBlock.gif    a2.start();
InBlock.gif    a3.start();
InBlock.gif
InBlock.gif    sleep(
1
);
InBlock.gif    CTimerManager::instance()
->
dump();
InBlock.gif    sleep(
1
);
InBlock.gif    CTimerManager::instance()
->
dump();
InBlock.gif    a.reset(
2000
);
InBlock.gif    a1.stop();
InBlock.gif    a3.stop();
InBlock.gif
InBlock.gif    sleep(
10
);
InBlock.gif    
return 0
;
ExpandedBlockEnd.gif}

八、后记
昨晚写好文章,不知为何无故丢失大半,郁闷。今早醒来,感觉还是有点地方要修改,:在start定时器线程的时候,传入精度和误差补偿,一是根据实际需要调整精确度,二是弥补延迟函数的稍许误差,以具有更好的伸缩性和精确度。本文旨在说明定时器模块的内部实现机制,详细细节不再修改了。

你可能感兴趣的:(技术系列之 定时器(一))