定时器之timerfd

2,949 阅读4分钟

前言

服务端遇到定时任务是很常见的,主要包括两类: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;
}
 
使用方法
  1. 可以单独使用
Timer timer;
   timer.initAlarm(ServerDefaultSetting::kDuccUpdateInterval, 0);
   DuccServer duccServer;
   while (!isStop())
   {
       timer.waitAlarm();
   }
  1. 结合IO复用
<span  color="red">将timer_create创建的fd加入即可,需要注意的是要读取fd上的超时时间,否则可能会一直触发IO可读。</span>

优缺点

  1. 优点
  • timerfd_create 把时间变成了一个文件描述符,该“文件”在定时器超时的那一刻变得可读,这样就能很方便地融入到 select/poll 框架中,用统一的方式来处理 IO 事件和超时事件。
  • 利用select, poll的timeout实现定时功能,它们的缺点是定时精度只有毫秒,远低于 timerfd_settime 的定时精度。
  1. 缺点
  • 在于IO复用一起使用的时,需要注意超时之后需要read一下,否则可能会一直触发。
  • 注意os的版本(centos5可能不支持,一般是支持的)