news 2026/7/2 2:02:53

timefd

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
timefd

timefd 到底是什么

你可以把timefd理解成“把定时器变成文件描述符(fd)的工具”—— Linux 系统把定时器功能包装成了一个和 “文件、网络套接字” 一样的 fd,你可以像操作文件一样操作定时器。

为什么要用它?新手可能用过alarm()这类简单定时器,但它们有坑:比如依赖信号处理(容易和其他信号冲突)、多线程下同步麻烦。而timefd把定时器变成 fd 后,能和epoll/poll(Linux 处理多 IO 事件的工具)结合,既不用处理复杂的信号,又能把 “定时器事件” 和 “网络 IO、文件 IO 事件” 放在一起管理,特别适合写服务程序。

核心特点:

  1. 定时器超时后,这个 fd 会变成 “可读” 状态,用read()就能读到超时信息;
  2. 支持两种定时器:一次性(比如 3 秒后响一次)、周期性(比如每 2 秒响一次)。

timefd 核心函数

使用timefd需要先包含头文件<sys/timerfd.h>,编译时部分 Linux 版本需要加-lrt(新版一般不用)。下面逐个讲核心函数,参数、返回值都用大白话解释,不用记复杂表格。

1. 创建定时器 fd:timerfd_create
int timerfd_create(int clockid, int flags);

作用:创建一个和定时器绑定的 fd,后续所有定时器操作都靠这个 fd 完成。

  • 参数 1:clockid(时钟类型)新手只需要选CLOCK_MONOTONIC!它代表 “系统开机后流逝的时间”,只会一直增加,哪怕手动改系统时间(比如把时间调回 1 小时前),这个时间也不会变,定时器不会乱。另一个值CLOCK_REALTIME是 “系统当前时间”,改系统时间会导致定时器出错,新手别用。

  • 参数 2:flags(标志位)新手推荐写TFD_NONBLOCK | TFD_CLOEXEC

    • TFD_NONBLOCK:把 fd 设为 “非阻塞”(读不到数据时不会卡主程序);
    • TFD_CLOEXEC:程序启动新进程时自动关闭这个 fd,避免 fd 泄漏。如果你想先简单理解,也可以设为 0(阻塞模式)。
  • 返回值:

    • 成功:返回一个整数(就是这个定时器的 fd 编号,比如 3、4);
    • 失败:返回 -1,系统会设置errno(可以用strerror(errno)看具体错在哪)。
2. 设置定时器规则:timerfd_settime
int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value);

作用:告诉系统 “这个定时器什么时候超时、要不要重复”。

  • 参数 1:fd就是timerfd_create返回的定时器 fd,指定要设置哪个定时器。

  • 参数 2:flags新手先设为 0!代表 “超时时间是相对时间”(比如 “3 秒后超时”);如果设为TFD_TIMER_ABSTIME,就是 “绝对时间”(比如 “开机后 100 秒超时”),新手暂时用不上。

  • 参数 3:new_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 = 3(3 秒后第一次超时);
    • 周期性定时器:it_interval.tv_sec = 2(每 2 秒重复),it_value.tv_sec = 1(1 秒后第一次超时)。
  • 参数 4:old_value新手直接设为 NULL!它的作用是 “返回之前的定时器规则”,暂时用不上。

  • 返回值:

    • 成功:返回 0;
    • 失败:返回 -1,用strerror(errno)能看到错误原因(比如 fd 无效)。
3. 查看定时器剩余时间:timerfd_gettime
int timerfd_gettime(int fd, struct itimerspec *curr_value);

作用:查一下这个定时器还剩多久才会超时。

  • 参数 1:fd目标定时器的 fd;
  • 参数 2:curr_value传一个空的itimerspec结构体就行,调用后系统会把 “剩余时间” 填到这个结构体里(比如curr_value->it_value.tv_sec就是剩余的秒数);
  • 返回值:
    • 成功:返回 0;
    • 失败:返回 -1。
4. 读取定时器超时事件:read(普通的 read 函数)

新手重点:定时器超时后,它的 fd 会变成 “可读” 状态,必须用read()读取,否则 fd 会一直处于 “可读” 状态,反复触发事件!

函数原型(就是普通的 read,不用记新函数):

ssize_t read(int fd, void *buf, size_t count);
  • 参数 1:fd:定时器的 fd;
  • 参数 2:buf:要存一个uint64_t类型的变量(8 字节),这个变量会记录 “累计超时次数”(比如定时器该超时 2 次但你没读,这里就会是 2);
  • 参数 3:count:必须填 8(因为uint64_t是 8 字节);
  • 返回值:
    • 成功:返回 8(表示读到了 8 字节的超时次数);
    • 失败:返回 -1(比如没超时就读,非阻塞模式下会返回 -1,且errno=EAGAIN)。

样例

样例 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(1); } int main() { // 步骤1:创建定时器fd(阻塞模式,新手先不用非阻塞) int tfd = timerfd_create(CLOCK_MONOTONIC, 0); if (tfd == -1) { 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) == -1) { error_exit("设置定时器规则失败"); } std::cout << "步骤2:定时器设置完成,3秒后超时..." << std::endl; // 步骤3:等待定时器超时并读取事件(阻塞模式下,read会等直到超时) uint64_t timeout_count = 0; // 存超时次数 ssize_t read_ret = read(tfd, &timeout_count, 8); // 第三个参数必须是8 if (read_ret == -1) { error_exit("读取定时器超时事件失败"); } std::cout << "步骤3:定时器超时!累计超时次数:" << timeout_count << std::endl; // 步骤4:查看定时器剩余时间(超时后剩余时间应该是0) struct itimerspec remain_time; if (timerfd_gettime(tfd, &remain_time) == -1) { 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++ -std=c++11 timerfd_demo1.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(1); } // 新手友好:打印当前时间(毫秒),方便看效果 uint64_t get_ms() { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return ts.tv_sec * 1000 + ts.tv_nsec / 1000000; } int main() { // 步骤1:创建非阻塞的定时器fd int tfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); if (tfd == -1) error_exit("创建定时器失败"); std::cout << "[" << get_ms() << "] 定时器fd创建成功" << std::endl; // 步骤2:设置周期性定时器:1秒后首次超时,每2秒重复一次 struct itimerspec timer_rule; timer_rule.it_value.tv_sec = 1; // 1秒后第一次超时 timer_rule.it_value.tv_nsec = 0; timer_rule.it_interval.tv_sec = 2; // 每2秒重复一次 timer_rule.it_interval.tv_nsec = 0; if (timerfd_settime(tfd, 0, &timer_rule, NULL) == -1) { error_exit("设置定时器规则失败"); } std::cout << "[" << get_ms() << "] 周期性定时器设置完成:1秒后首次超时,每2秒重复" << std::endl; // 步骤3:初始化poll,监听定时器fd的“可读”事件 struct pollfd pfd; pfd.fd = tfd; // 要监听的fd是定时器fd pfd.events = POLLIN; // 监听“可读”事件(定时器超时后fd可读) pfd.revents = 0; // 初始化,不用改 // 步骤4:非阻塞事件循环(主线程可以做其他事) int count = 0; // 记录超时次数,到3次就退出 while (true) { // poll监听:每100毫秒检查一次(非阻塞) int poll_ret = poll(&pfd, 1, 100); if (poll_ret == -1) { if (errno == EINTR) continue; // 信号中断,继续循环 error_exit("poll监听失败"); } // 情况1:poll超时(100ms内没事件),主线程做其他事 if (poll_ret == 0) { std::cout << "[" << get_ms() << "] 主线程空闲中...(定时器未超时)" << std::endl; continue; } // 情况2:定时器fd可读(超时了) if (pfd.revents & POLLIN) { uint64_t timeout_count = 0; ssize_t read_ret = read(tfd, &timeout_count, 8); if (read_ret != 8) error_exit("读取超时事件失败"); std::cout << "[" << get_ms() << "] 定时器超时!累计超时次数:" << timeout_count << std::endl; count++; if (count >= 3) { // 超时3次后退出 std::cout << "[" << get_ms() << "] 超时3次,退出程序" << std::endl; break; } } } // 步骤5:关闭fd close(tfd); return 0; }

编译运行命令:

g++ -std=c++11 timerfd_demo2.cpp -o timerfd_demo2 -lrt ./timerfd_demo2

运行效果:

[123456] 定时器fd创建成功 [123457] 周期性定时器设置完成:1秒后首次超时,每2秒重复 [123557] 主线程空闲中...(定时器未超时) [123657] 主线程空闲中...(定时器未超时) [123757] 定时器超时!累计超时次数:1 [123857] 主线程空闲中...(定时器未超时) [123957] 主线程空闲中...(定时器未超时) [124057] 定时器超时!累计超时次数:1 [124157] 主线程空闲中...(定时器未超时) [124257] 主线程空闲中...(定时器未超时) [124357] 定时器超时!累计超时次数:1 [124357] 超时3次,退出程序
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/1 9:57:42

华为MetaERP的成本优势不仅在于直接省掉软件许可费,更体现在通过全栈自研的技术架构和智能化能力,从长期运营中实现“极低成本”。

华为MetaERP的成本优势不仅在于直接省掉软件许可费&#xff0c;更体现在通过全栈自研的技术架构和智能化能力&#xff0c;从长期运营中实现“极低成本”。其优势具体如下表所示&#xff1a;成本优势类别具体表现关键数据/案例支撑直接成本削减软件许可费归零&#xff1a;替代Or…

作者头像 李华
网站建设 2026/7/1 16:33:39

一篇文章讲清楚:中转API如何用Python调用ChatGPT

你是否遇到过这样的场景&#xff1f; 想把 ChatGPT 接入自己的应用&#xff0c;却被模型限制、价格波动、接口差异搞得一头雾水&#xff1b; 刚写好的代码&#xff0c;换一个模型就要重构一遍&#xff1b; 好不容易跑通了&#xff0c;又发现前端、后端、流式输出全都要重新适配…

作者头像 李华
网站建设 2026/7/1 10:12:51

深度测评9个论文写作工具,自考学生一键生成论文工具推荐!

深度测评9个论文写作工具&#xff0c;自考学生一键生成论文工具推荐&#xff01; 自考论文写作的革新之路 随着人工智能技术的飞速发展&#xff0c;越来越多的学生开始借助AI工具来提升论文写作效率。尤其是在自考群体中&#xff0c;面对繁重的学习任务和严格的论文要求&#x…

作者头像 李华
网站建设 2026/7/1 9:57:49

Product Hunt 每日热榜 | 2026-01-25

1. Humans in the Loop 标语&#xff1a;一个免费的社区&#xff0c;专门讨论与代理编程与人工智能相关的所有事。 介绍&#xff1a;“有人的参与”是一个为热爱利用人工智能加速工作的伙伴们打造的地方。这里是一个免费的社区&#xff0c;大家可以在这里分享关于Claude Code…

作者头像 李华
网站建设 2026/7/2 0:16:24

MATLAB实现基于二阶锥松弛技术的主动配电网故障重构模型与可视化展示

MATLAB代码&#xff1a;基于二阶锥松弛的主动配电网故障重构及可视化 关键词&#xff1a;配电网 故障重构 二阶锥松弛 可视化 参考文档&#xff1a;《基于禁忌克隆遗传算法的配电网故障恢复重构_张利民》参考故障重构部分模型&#xff1b;《二阶锥松弛在配电网最优潮流计算中的…

作者头像 李华