陈硕在《Linux多线程服务端编程:使用muduo C++网络库》中说到(4.10多线程和signal):多线程程序中,使用signal的第一原则就是不要使用signal
。原因是信号处理函数的复杂性和限制性,以及中断处理的困难,可能导致正确程序编写的困难,在这里陈硕举例signal handler函数中修改全局变量但是由于编译器优化导致修改不能被立刻看到的情况,以及多线程中掩码的考虑和signal hander中不能通过codition variable来通知其他线程,不能再信号处理函数中创建其他线程等。
但是在之前的定时函数的实现中,我都是用alarm+信号处理函数+统一事件源,来处理定时任务。
所以需要了解一下timerfd_*系列系统调用,以及gettimeofday()函数。
虽然网上文章不少,但是笔者认为吸收别人的意见并用自己的话写下来和只看别人的文章是不一样的,加上每个人的侧重点也不同,所以有必要写下来。
参考文章:
linux timerfd系列函数总结
Linux的timerfd分析
一、timerfd_*
当定时器超时时候,定时器对象代表的文件描述符可读,也就是可以用read函数读取,而且要
注意
read读取的是uint64_t
类型的值,如果不是read可能会读取失败。
uint64_t exp = 0;
int ret = read(tmfd, &(exp), sizeof(uint64_t));
头文件
#include <sys/timerfd.h>
1.1 int timerfd_create(int clockid, int flags);
timerfd_create创建一个定时器对象,并返回对应的文件描述符
- clockid:clockid标识指定的时钟计数器,可选值:
- CLOCK_REALTIME:系统实时时间,
随系统实时时间改变而改变
,即从UTC1970-1-1 0:0:0开始计时,中间时刻如果系统时间被用户改成其他,则对应的时间相应改变 - CLOCK_MONOTONIC:从系统启动这一刻起开始计时,不受系统时间被用户改变的影响
- 如果不想系统时间的修改导致计时器计时时间的改变,或者不知道选什么的时候,可能
CLOCK_MONOTONIC
会是一个更好的选择。
- CLOCK_REALTIME:系统实时时间,
- flags:可选下边两个的与值,timerfd默认是阻塞模式,
- TFD_NONBLOCK(非阻塞模式)
- TFD_CLOEXEC(表示当程序执行exec函数时本fd将被系统自动关闭,表示不传递)
1.2 timerfd_settime()函数
int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value);
//设置定时器
struct itimerspec {
struct timespec it_interval; /* Interval for periodic timer (定时间隔周期)*/
struct timespec it_value; /* Initial expiration (第一次超时时间)*/
};
//
struct timespec {
time_t tv_sec; /* Seconds */
long tv_nsec; /* Nanoseconds */
};
- timerfd_settime()此函数用于设置新的超时时间,并开始计时,能够启动和停止定时器,停止定时器也就是把时间间隔改为0。
- 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表示停止定时器
1.3 int timerfd_gettime(int fd, struct itimerspec *curr_value);
- timerfd_gettime()函数获取距离下次超时剩余的时间。
- curr_value.it_value 字段表示距离下次超时的时间,若值为0,表示计时器已经解除,其表示的值永远是一个相对值,无论TFD_TIMER_ABSTIME是否被设置
- curr_value.it_interval 定时器间隔时间
例子:
从第一秒开始,每一秒触发一次。
#include <sys/timerfd.h>
#include <sys/time.h>
#include "fcntl.h"
#include "unistd.h"
#include <stdio.h>
#include <stdint.h>
#include <fstream>
using namespace std;
int main(){
int tmfd;
int ret;
struct itimerspec new_value;
struct timeval tv;
gettimeofday(&tv,NULL);
new_value.it_value.tv_sec = 1;
new_value.it_value.tv_nsec = 0;
new_value.it_interval.tv_sec = 1;
new_value.it_interval.tv_nsec = 0;
//创建定时器
tmfd = timerfd_create(CLOCK_REALTIME, TFD_CLOEXEC);
if (tmfd == -1)
{
printf("timerfd_create fail\n");
return 0;
}
//用相对时间设置定时器
timerfd_settime(tmfd, 0, &new_value, NULL);
for(int i = 0; i != 10; ++i){
uint64_t exp = 0;
//由于设置为阻塞,所以read返回的时候定时器一定是被触发了
int ret = read(tmfd, &(exp), sizeof(uint64_t));
printf("%d sec, ret = %d, exp = %u\n", i, ret, exp);
}
close(tmfd);
return 0;
}