核心内容摘要
聂小雨大战老黑:结局扑朔迷离,谁才是最终赢家?
timefd 到底是什么你可以把timefd理解成“把定时器变成文件描述符fd的工具”—— Linux 系统把定时器功能包装成了一个和 “文件、网络套接字” 一样的 fd你可以像操作文件一样操作定时器。
为什么要用它新手可能用过alarm()这类简单定时器但它们有坑比如依赖信号处理容易和其他信号冲突、多线程下同步麻烦。
而timefd把定时器变成 fd 后能和epoll/pollLinux 处理多 IO 事件的工具结合既不用处理复杂的信号又能把 “定时器事件” 和 “网络 IO、文件 IO 事件” 放在一起管理特别适合写服务程序。
核心特点定时器超时后这个 fd 会变成 “可读” 状态用read()就能读到超时信息支持两种定时器一次性比如 3 秒后响一次、周期性比如每 2 秒响一次。
timefd 核心函数使用timefd需要先包含头文件sys/timerfd.h编译时部分 Linux 版本需要加-lrt新版一般不用。
下面逐个讲核心函数参数、返回值都用大白话解释不用记复杂表格。
创建定时器 fdtimerfd_createint timerfd_create(int clockid, int flags);作用创建一个和定时器绑定的 fd后续所有定时器操作都靠这个 fd 完成。
参数 1clockid时钟类型新手只需要选CLOCK_MONOTONIC它代表 “系统开机后流逝的时间”只会一直增加哪怕手动改系统时间比如把时间调回 1 小时前这个时间也不会变定时器不会乱。
另一个值CLOCK_REALTIME是 “系统当前时间”改系统时间会导致定时器出错新手别用。
参数 2flags标志位新手推荐写TFD_NONBLOCK | TFD_CLOEXECTFD_NONBLOCK把 fd 设为 “非阻塞”读不到数据时不会卡主程序TFD_CLOEXEC程序启动新进程时自动关闭这个 fd避免 fd 泄漏。
如果你想先简单理解也可以设为 0阻塞模式。
返回值成功返回一个整数就是这个定时器的 fd 编号比如
4失败返回 -1系统会设置errno可以用strerror(errno)看具体错在哪。
设置定时器规则timerfd_settimeint timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value);作用告诉系统 “这个定时器什么时候超时、要不要重复”。
参数 1fd就是timerfd_create返回的定时器 fd指定要设置哪个定时器。
参数 2flags新手先设为 0代表 “超时时间是相对时间”比如 “3 秒后超时”如果设为TFD_TIMER_ABSTIME就是 “绝对时间”比如 “开机后 100 秒超时”新手暂时用不上。
参数 3new_value核心设置超时规则这是一个结构体新手不用记复杂定义只需要知道它管两个事struct itimerspec { struct timespec it_interval; // 定时器的重复间隔多久重复一次 struct timespec it_value; // 定时器第一次超时的时间 }; // 其中 timespec 结构体只需要关注这两个值 struct timespec { time_t tv_sec; // 秒比如 3 就是 3 秒 long tv_nsec; // 纳秒1秒10亿纳秒新手直接设 0 就行 };一次性定时器it_interval.tv_sec 0不重复it_value.tv_sec 33 秒后第一次超时周期性定时器it_interval.tv_sec 2每 2 秒重复it_value.tv_sec 11 秒后第一次超时。
参数 4old_value新手直接设为 NULL它的作用是 “返回之前的定时器规则”暂时用不上。
返回值成功返回 0失败返回 -1用strerror(errno)能看到错误原因比如 fd 无效。
查看定时器剩余时间timerfd_gettimeint timerfd_gettime(int fd, struct itimerspec *curr_value);作用查一下这个定时器还剩多久才会超时。
参数 1fd目标定时器的 fd参数 2curr_value传一个空的itimerspec结构体就行调用后系统会把 “剩余时间” 填到这个结构体里比如curr_value-it_value.tv_sec就是剩余的秒数返回值成功返回 0失败返回 -1。
读取定时器超时事件read普通的 read 函数新手重点定时器超时后它的 fd 会变成 “可读” 状态必须用read()读取否则 fd 会一直处于 “可读” 状态反复触发事件函数原型就是普通的 read不用记新函数ssize_t read(int fd, void *buf, size_t count);参数 1fd定时器的 fd参数 2buf要存一个uint64_t类型的变量8 字节这个变量会记录 “累计超时次数”比如定时器该超时 2 次但你没读这里就会是 2参数 3count必须填 8因为uint64_t是 8 字节返回值成功返回 8表示读到了 8 字节的超时次数失败返回 -1比如没超时就读非阻塞模式下会返回 -1且errnoEAGAIN。
样例样例 1最简单的一次性定时器阻塞版新手先跑这个理解核心流程代码注释写满复制就能编译运行。
#include iostream // timefd 必需的头文件 #include sys/timerfd.h #include unistd.h #include cstdint // 用于 uint64_t存超时次数 #include cerrno // 用于 errno错误码 #include cstring // 用于 strerror转错误码为文字 #include cstdlib // 用于 exit退出程序 // 新手友好的错误处理出错时打印信息并退出 void error_exit(const char *msg) { std::cerr 错误 msg - strerror(errno) std::endl; exit(
; } int main() { // 步骤1创建定时器fd阻塞模式新手先不用非阻塞 int tfd timerfd_create(CLOCK_MONOTONIC,
; if (tfd -
{ error_exit(创建定时器失败); } std::cout 步骤1定时器fd创建成功fd号 tfd std::endl; // 步骤2设置定时器规则3秒后超时一次性不重复 struct itimerspec timer_rule; // 第一次超时时间3秒0纳秒 timer_rule.it_value.tv_sec 3; timer_rule.it_value.tv_nsec 0; // 重复间隔0秒一次性不重复 timer_rule.it_interval.tv_sec 0; timer_rule.it_interval.tv_nsec 0; // 调用 timerfd_settime 设置规则 if (timerfd_settime(tfd, 0, timer_rule, NULL) -
{ error_exit(设置定时器规则失败); } std::cout 步骤2定时器设置完成3秒后超时... std::endl; // 步骤3等待定时器超时并读取事件阻塞模式下read会等直到超时 uint64_t timeout_count 0; // 存超时次数 ssize_t read_ret read(tfd, timeout_count,
; // 第三个参数必须是8 if (read_ret -
{ error_exit(读取定时器超时事件失败); } std::cout 步骤3定时器超时累计超时次数 timeout_count std::endl; // 步骤4查看定时器剩余时间超时后剩余时间应该是0 struct itimerspec remain_time; if (timerfd_gettime(tfd, remain_time) -
{ error_exit(获取剩余时间失败); } std::cout 步骤4定时器剩余超时时间 remain_time.it_value.tv_sec 秒 std::endl; // 步骤5关闭fd新手别忘用完必须关 close(tfd); std::cout 步骤5定时器fd已关闭 std::endl; return 0; }编译运行命令g -stdc11 timerfd_demo
cpp -o timerfd_demo1 -lrt ./timerfd_demo1运行效果步骤1定时器fd创建成功fd号3 步骤2定时器设置完成3秒后超时... 等待3秒 步骤3定时器超时累计超时次数1 步骤4定时器剩余超时时间0秒 步骤5定时器fd已关闭样例 2实用的周期性定时器非阻塞 poll实际开发中不会用阻塞 read而是用 poll 监听主线程还能做其他事。
#include iostream #include sys/timerfd.h #include sys/poll.h // poll 头文件监听fd事件 #include unistd.h #include cstdint #include cerrno #include cstring #include cstdlib void error_exit(const char *msg) { std::cerr 错误 msg - strerror(errno) std::endl; exit(