一、 基础知识
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 1000 0 -1000

usleep 1000 2974 1974

nanosleep 1000 2990 1990

select 1000 991 -9

pselect 1000 990 -10

gettimeofday 1000 1000 0
而使用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:

static void * 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使用方式


static unsigned int mark; //测试,配合dump

};

/*singlton的double-check实现*/

CTimerManager * CTimerManager::instance()

{

if(m_instance==NULL)

{

pthread_mutex_lock(&m_mutex);

if(m_instance==NULL)

{

m_instance=new CTimerManager();

}

pthread_mutex_unlock(&m_mutex);

}

return m_instance;

}

/*process必须static,不能操作非static属性,因此传递this指针*/

void CTimerManager:: start()

{
if(m_state==TIMER_MANAGER_STOP){

m_state=TIMER_MANAGER_START;

pthread_t pid;

pthread_create(&pid,0,process,this);
}

}

/*定时器模块延迟时间线程*/

void * CTimerManager:: process(void * arg)

{

pthread_detach(pthread_self());


CTimerManager *manage=(CTimerManager *)arg;


CTimer *item;

struct timeval start,end;

unsigned int delay;

struct timeval tm;

gettimeofday(&end,0);

/*使用状态控制线程运行,进而容易实现stop,也可以使用pthread_cancel粗暴的停止,需要考虑暂停点等问题*/

while(manage->m_state==TIMER_MANAGER_START)

{

tm.tv_sec=0;

tm.tv_usec=DEFULT_INTERVAL*1000;

start.tv_sec=end.tv_sec;

start.tv_usec=end.tv_usec;

/*不同系统的延迟函数精度不同,如果需要替换为其他延迟函数,这附近修改下就好*/

while(select(0,0,0,0,&tm)<0&&errno==EINTR);

gettimeofday(&end,0);


delay=(end.tv_sec-start.tv_sec)*1000+(end.tv_usec-start.tv_usec)/1000;

pthread_mutex_lock(&manage->m_mutex);


LIST_FOREACH(item, &(manage->list_), entry_)

{

item->m_counter<delay?item->m_counter=0:item->m_counter-=delay;

if(item->m_counter==0)

{

if(item->m_func)

item->m_func(item,item->m_data);


if(item->m_type==CTimer::TIMER_ONCE)

{

/*一次型的,超时,移除,并状态CTimer::TIMER_TIMEOUT*/

manage->remove_timer_(item);

item->m_state=CTimer::TIMER_TIMEOUT;

}

else if(item->m_type==CTimer::TIMER_CIRCLE)

{

/*循环型的,重置counter就好*/

item->m_counter=item->m_interval;

}

}

}


pthread_mutex_unlock(&manage->m_mutex);

}

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

#include "timer_manager.h"

#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

void func(CTimer * timer, void *data)

{

printf("hi,%d\n",(int)(data));

}

/*随便写的,凑合着看吧。没有CTimerManager::instance()->stop();也没new对象。定时器对象可多次start和stop,使用上对暴露的接口没有任何的契约式限制,可随意调用*/

int main()

{

CTimerManager::instance()->start();

CTimer a(1000,func,(void *)1,CTimer::TIMER_CIRCLE);

CTimer a1(2000,func,(void *)11,CTimer::TIMER_ONCE);

CTimer a2(3000,func,(void *)12,CTimer::TIMER_ONCE);

CTimer a3(1000,func,(void *)13,CTimer::TIMER_ONCE);

a.start();

a1.start();

a2.start();

a3.start();

a.start();

a1.start();

a2.start();

a3.start();


sleep(1);

CTimerManager::instance()->dump();

sleep(1);

CTimerManager::instance()->dump();

a.reset(2000);

a1.stop();

a3.stop();


sleep(10);

return 0;

}
- Linux下系统时间函数、DST等相关问题总结(转)
Linux下系统时间函数.DST等相关问题总结 下面这个结构体存储了跟时区相关的位移量(offset)以及是否存在DST等信息,根据所在的时区信息,很容易找到系统时间与UTC时间之间的时区偏移,另外根 ...
- 4412 linux延时和时间
基本知识 • linux中延时函数很简单,却经常用到• 在操作系统中和单片机处理延时方式就完全不一样了,不可能是使用for循环浪费系统资源.而是有专门的接口函数• linux系统编程中常用的延时函数: ...
- linux与php时间函数有关的错误解决
最近在程序里写了不少获取时间或时间戳的函数date() strtotime()等,但是把程序拿到linux上运行却爆出这些函数的错误,具体原因是因为linux本身的时间设置以及php的时区问题. 先确 ...
- linux几种时间函数总结
一.linux时间函数总结 最近的工作中用到的时间函数比较频繁,今天抽时间总结一下,在linux下,常用的获取时间的函数有如下几个: asctime, ctime, gmtime, localti ...
- (笔记)Linux下的ioctl()函数详解
我这里说的ioctl函数是指驱动程序里的,因为我不知道还有没有别的场合用到了它,所以就规定了我们讨论的范围.写这篇文章是因为我前一阵子被ioctl给搞混了,这几天才弄明白它,于是在这里清理一下头脑. ...
- linux中时间函数
linux下常用时间类型有四种: time_t . struct tm. struct timeval . struct timespec 1.time_t 时间函数 time_t ...
- (笔记)Linux下的准确延时,#include <linux/delay.h>调用出错
在编写应用层程序时,有时需要延时一下,这个时候该怎么办呢? 在内核代码中,我们经常会看到这样的头文件使用#include <linux/delay.h>,心想着直接调用这个就可以了吧!可是 ...
- Linux下精确控制时间的函数
Linux下精确控制时间的函数 在测试程序接口运行时间的时候,常用time,gettimeofday等函数,但是这些函数在程序执行的时候是耗费时间的,如果仅仅测试时间还行,但是如果程序中用到时间控制类 ...
- (笔记)Linux下system()函数的深度理解(整理)
注:从其它地方转的非常好的一篇文章,值得深究! 这几天调程序(嵌入式linux),发现程序有时就莫名其妙的死掉,每次都定位在程序中不同的system()函数,直接在shell下输入system()函数 ...
随机推荐
- android:3D垂直翻转动画-FlipAnimation
需求 对ImageView进行相似于翻纸牌的动画 解决 各种Animator的组合 第一步动画: 动画代码文件1,card_flip_left_out.xml <? xml version=&q ...
- C++中的typedef typename 作用
今天在代码里看到了这样一段代码: typedef typename RefBase::weakref_type weakref_type; 起初一直搞不懂为什么要加个typename,后来搜索了一下才 ...
- 如何使用KVM 虚拟机安装RHEL7系统
KVM(基于内核的虚拟机)是标准的RHEL内核中内置的完整的虚拟化解决方案.它可以运行多款未经修改的Windows和Linux虚拟客户机操作系统.RHEL中的KVM系统管理程序通过libvirtAPI ...
- 每日英语:Why Mom's Time Is Different From Dad's Time
Several years ago, while observing a parenting group in Minnesota, I was struck by a confession one ...
- 【Android】事件处理系统
linux输入子系统 Android是linux内核的,所以它的事件处理系统也在linux的基础上完成的. Linux内核提供了一个Input子系统来实现的,Input子系统会在/dev/input/ ...
- 转webstorm的快捷键
止 静 java android 转-webstorm快捷键 默认配置-Eclipse的常用快捷键对照表 查找/代替 Webstorm快捷键 Eclipse快捷键 说明 ctrl+shift+N ct ...
- Beginning SDL 2.0(6) 音频渲染及wav播放
前面几篇关于SDL的文章介绍的是以画面为主,这里介绍下SDL中针对音频播放提供的机制,以及如何应用. 对于音频而言,有几个概念需要事先了解下,采样率.声道数.量化位数,如果你不清楚的话,麻烦先了解下这 ...
- win7下memCache安装过程
1.下载memcache 的windows 稳定版,解压放某个盘下面,比如在H:/wamp/www/php api/memcache: 2.在终端(即cmd 命令界面)下,输入安装命令 :H:/wam ...
- Ansible 进阶技巧
原文 http://www.ibm.com/developerworks/cn/linux/1608_lih_ansible/index.html?ca=drs- 简介 Ansible 是一个系 ...
- 微信扫码支付.net版本
微信扫码支付有两个坑 1.模式一已经过时,不能使用了 2.HttpService类的POST 和 GET方法内的 //设置代理WebProxy proxy = new WebProxy();proxy ...