Linux下定时器
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下定时器的更多相关文章
- linux下定时器的实现
简介: linux下经常有这样的需求,需要定时轮询执行某种任务,当然,用shell脚本的话,crontab和at就可以满足要求.如果从C语言的角度来看,实现定时器也是一个比较简单的任务,因为具有普遍性 ...
- linux下定时器介绍1
POSIX Timer 间隔定时器 setitimer 有一些重要的缺点,POSIX Timer 对 setitimer 进行了增强,克服了 setitimer 的诸多问题: 首先,一个进程同一时刻只 ...
- linux下定时器介绍2--timer_create等函数集的使用示例
程序1:采用新线程派驻的通知方式 程序2:通知方式为信号的处理方式 #include <stdio.h>#include <time.h>#include <stdlib ...
- Linux下的微秒级定时器: usleep, nanosleep, select, pselect
Linux下的微秒级定时器: usleep, nanosleep, select, pselect 标签: linuxnulldelaystructdate 2012-02-07 23:29 4979 ...
- Linux下的定时器:alarm()与setitimer()
Linux下的定时器有两种,以下分别介绍: 1.alarm 如果不要求很精确的话,用alarm()和signal()就够了 unsigned int alarm(unsigned int second ...
- Linux下的定时器
以下摘自linux下的man文件:(man getitimer) #include <sys/time.h> int getitimer(int which, struct iti ...
- Linux下的定时器类实现(select定时+线程)
更好的计时器类实现:LINUX RTC机制实现计时器类(原创) 很多时候需要在LINUX下用到定时器,但像setitimer()和alarm()这样的定时器有时会和sleep()函数发生冲突,这样就给 ...
- Linux下一种高效多定时器实现
Linux下一种高效多定时器实现 作者:LouisozZ 日期:2018.08.29 运行环境说明 由于在 Linux 系统下一个进程只能设置一个时钟定时器,所以当应用需要有多个定时器来共同管理程序运 ...
- 在Linux下的中断方式读取按键驱动程序
// 在Linux下的中断方式读取按键驱动程序 //包含外部中断 休眠 加入poll机制 // 采用异步通知的方式 // 驱动程序发 ---> app接收 (通过kill_fasync()发送) ...
随机推荐
- Qt5.9使用QWebEngineView加载网页速度非常慢,问题解决
折腾了大半天终于解决了 原帖地址:https://bugreports.qt.io/browse/QTBUG-44763 BUG单下的留言讲明了问题发生的原因,那就是系统默认设置为自动寻找代理,而使用 ...
- Centos6.5网络配置
由于项目部署的需要,不得不继续研究Linux,前期看过一些Linux方面的资料,也动手配置过Linux网络配置,但是由于开发项目一般在windows下进行的,用Linux比较少,所以基本上也就忘记以前 ...
- B. Math
链接 [http://codeforces.com/contest/1062/problem/B] 题意 给你n,有两种操作要么乘以某个数,要么开根但必须开根后是整数才能开,问你最后能变成最小的数是多 ...
- 【Beta版本发布】爬虫队长装备全面更新!
一.Beta阶段目标回顾 1.为了解决Alpha阶段线程异常泛滥的问题,我们需要一个线程池. 2.为了爬取得到的文件正确可用,我们需要一个异常清理器. 3.为了不间断爬取,管理员不必频繁运行程序点,我 ...
- maven配置私服
1先配置maven的配置文件 2在项目的pom.xml文件增加 <distributionManagement> <repository> <id>nexus-re ...
- [转]Spring通过@Value注解注入属性的几种方式
原文地址:https://blog.csdn.net/csujiangyu/article/details/50945486 ------------------------------------- ...
- apache Storm学习之二-基本概念介绍
2.1 Storm基本概念 在运行一个Storm任务之前,需要了解一些概念: Topologies Streams Spouts Bolts Stream groupings Reliability ...
- vue的自定义组件和组件传值
<div id="app"> <div>{{pmessage}}</div> //父组件 <child :message="pm ...
- [cnblog新闻]历史性时刻:云硬件支出首次高于传统硬件
https://news.cnblogs.com/n/617487/ 据调研公司 IDC 声称,2018 年第三季度云硬件支出占 IT 总收入的 50.9%. 知名调研公司 IDC 声称,面向云的 I ...
- tomcat启动失败问题总结
一.端口占用 linux下启动tomcat的时候,如果服务启动不成功,可以到查看logs文件夹下的catalina.out 日志文件,cat我的日志文件可得到如下的错误信息:(Caused by: ...