前言
服务端遇到定时任务是很常见的,主要包括两类:once和repeat。常见的方案就是使用时间堆或时间片+I/O复用,今天给大家介绍一个更方便的方法:timerfd_xx。
接口说明
#include <sys/timerfd.h>
int timerfd_create(int clockid, int flags);
int timerfd_settime(int fd, int flags,
const struct itimerspec *new_value,
struct itimerspec *old_value);
int timerfd_gettime(int fd, struct itimerspec *curr_value);
timerfd_create
int timerfd_create(int clockid, int flags);
/*
* timerfd_create() 函数创建一个定时器对象,同时返回一个与之关联的文件描述符。
* clockid:clockid标识指定的时钟计数器,可选值(CLOCK_REALTIME、CLOCK_MONOTONIC。。。)
* CLOCK_REALTIME:系统实时时间,随系统实时时间改变而改变,即从UTC1970-1-1 0:0:0开始计时,中间时刻如果系统时间被用户改成其他,则对应的时间相应改变
* CLOCK_MONOTONIC:从系统启动这一刻起开始计时,不受系统时间被用户改变的影响
* flags:参数flags(TFD_NONBLOCK(非阻塞模式)/TFD_CLOEXEC(表示当程序执行exec函数时本fd将被系统自动关闭,表示不传递)
*/
timerfd_settime
int timerfd_settime(int fd, int flags,
const struct itimerspec *new_value,
struct itimerspec *old_value);
/*
* timerfd_settime()此函数用于设置新的超时时间,并开始计时,能够启动和停止定时器;
* fd: 参数fd是timerfd_create函数返回的文件句柄
* flags:参数flags为1代表设置的是绝对时间(TFD_TIMER_ABSTIME 表示绝对定时器);为0代表相对时间。
* new_value: 参数new_value指定定时器的超时时间以及超时间隔时间
* old_value: 如果old_value不为NULL, old_vlaue返回之前定时器设置的超时时间,具体参考timerfd_gettime()函数
*
* ** it_interval不为0则表示是周期性定时器。
* it_value和it_interval都为0表示停止定时器
*/
其中itimerspec结构如下:
/*
* struct timespec {
* time_t tv_sec; //秒
* long tv_nsec; //纳秒
* };
*
* struct itimerspec {
* struct timespec it_interval; //Interval for periodic timer (定时间隔周期)
* struct timespec it_value; //Initial expiration (第一次超时时间)
* };
*/
timerfd_gettime
int timerfd_gettime(int fd, struct itimerspec *curr_value);
/*此函数用于获得定时器距离下次超时还剩下的时间。如果调用时定时器已经到期,并且该定时器处于循环模式(设置超时时间时struct itimerspec::it_interval不为0),那么调用此函数之后定时器重新开始计时*/
使用说明
Timer类
#ifndef _TIMER_H__
#define _TIMER_H__
class Timer
{
public:
int initAlarm(unsigned long long n_second, unsigned long long n_nanoseconds);
int setAlarm(unsigned long long n_second, unsigned long long n_nanoseconds);
unsigned long long waitAlarm();
Timer();
~Timer();
private:
int m_timerFd;
};
#endif // !_TIMER_H__
Timer.cpp实现
#include "Timer.h"
#include <sys/timerfd.h>
#include <unistd.h>
Timer::Timer(){
m_timerFd=-1;
}
Timer::~Timer(){
if(m_timerFd!=-1){
close(m_timerFd);
m_timerFd=-1;
}
}
int Timer::initAlarm(unsigned long long n_second, unsigned long long n_nanoseconds){
/*
* 创建一个定时器对象
*/
/*
* timerfd_create() 函数创建一个定时器对象,同时返回一个与之关联的文件描述符。
* clockid:clockid标识指定的时钟计数器,可选值(CLOCK_REALTIME、CLOCK_MONOTONIC。。。)
* CLOCK_REALTIME:系统实时时间,随系统实时时间改变而改变,即从UTC1970-1-1 0:0:0开始计时,中间时刻如果系统时间被用户改成其他,则对应的时间相应改变
* CLOCK_MONOTONIC:从系统启动这一刻起开始计时,不受系统时间被用户改变的影响
* flags:参数flags(TFD_NONBLOCK(非阻塞模式)/TFD_CLOEXEC(表示当程序执行exec函数时本fd将被系统自动关闭,表示不传递)
*/
m_timerFd = timerfd_create(CLOCK_MONOTONIC, 0);
if(m_timerFd==-1){
return -1;
}
/*
* 设置定时器时间
*/
/*
* struct timespec {
* time_t tv_sec; //秒
* long tv_nsec; //纳秒
* };
*
* struct itimerspec {
* struct timespec it_interval; //Interval for periodic timer (定时间隔周期)
* struct timespec it_value; //Initial expiration (第一次超时时间)
* };
*/
struct itimerspec new_value;
new_value.it_value.tv_sec = n_second;
new_value.it_value.tv_nsec = n_nanoseconds;
new_value.it_interval.tv_sec = n_second;
new_value.it_interval.tv_nsec = n_nanoseconds;
/*
* timerfd_settime()此函数用于设置新的超时时间,并开始计时,能够启动和停止定时器;
* fd: 参数fd是timerfd_create函数返回的文件句柄
* flags:参数flags为1代表设置的是绝对时间(TFD_TIMER_ABSTIME 表示绝对定时器);为0代表相对时间。
* new_value: 参数new_value指定定时器的超时时间以及超时间隔时间
* old_value: 如果old_value不为NULL, old_vlaue返回之前定时器设置的超时时间,具体参考timerfd_gettime()函数
*
* ** it_interval不为0则表示是周期性定时器。
* it_value和it_interval都为0表示停止定时器
*/
return timerfd_settime(m_timerFd, 0, &new_value, NULL);
}
int Timer::setAlarm(unsigned long long n_second, unsigned long long n_nanoseconds){
struct itimerspec new_value;
new_value.it_value.tv_sec = n_second;
new_value.it_value.tv_nsec = n_nanoseconds;
new_value.it_interval.tv_sec = n_second;
new_value.it_interval.tv_nsec = n_nanoseconds;
return timerfd_settime(m_timerFd, 0, &new_value, NULL);
}
unsigned long long Timer::waitAlarm(){
unsigned long long tmp_exp = 0;
read(m_timerFd, &tmp_exp, sizeof(tmp_exp));
return tmp_exp;
}
使用方法
- 可以单独使用
Timer timer;
timer.initAlarm(ServerDefaultSetting::kDuccUpdateInterval, 0);
DuccServer duccServer;
while (!isStop())
{
timer.waitAlarm();
}
- 结合IO复用
<span color="red">将timer_create创建的fd加入即可,需要注意的是要读取fd上的超时时间,否则可能会一直触发IO可读。</span>
优缺点
- 优点
- timerfd_create 把时间变成了一个文件描述符,该“文件”在定时器超时的那一刻变得可读,这样就能很方便地融入到 select/poll 框架中,用统一的方式来处理 IO 事件和超时事件。
- 利用select, poll的timeout实现定时功能,它们的缺点是定时精度只有毫秒,远低于 timerfd_settime 的定时精度。
- 缺点
- 在于IO复用一起使用的时,需要注意超时之后需要read一下,否则可能会一直触发。
- 注意os的版本(centos5可能不支持,一般是支持的)