当前位置: 首页 > news >正文

TimerFd Epoll

  1. “传统 Reactor:用 select(2)/poll(2)等待时间实现定时”;
  2. “现代 Linux:用 timerfd 把定时当作可读 fd纳入同一事件循环”。

方案 A:用 select(2) 的超时实现定时

要点:把“下一次到期时间”换算成 struct timeval timeout 传入 selectselect 返回后先处理 I/O,再检查“当前时间 ≥ 到期时间”来触发定时回调,并据此计算下一次的超时。

// build: gcc -Wall -O2 select_timer.c -o select_timer
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <unistd.h>
#include <sys/time.h>
#include <errno.h>static long long now_ms(void) {struct timeval tv; gettimeofday(&tv, NULL);return (long long)tv.tv_sec * 1000 + tv.tv_usec / 1000;
}int main(void) {// 模拟:每 1000ms 触发一次“定时任务”long long interval = 1000;long long next_expire = now_ms() + interval;// 示意:监听 0 号 fd(stdin)作为 I/O 事件源int maxfd = 0;for (;;) {long long n = now_ms();long long remain = next_expire - n;if (remain < 0) remain = 0;struct timeval tv;tv.tv_sec  = remain / 1000;tv.tv_usec = (remain % 1000) * 1000;fd_set rfds;FD_ZERO(&rfds);FD_SET(0, &rfds); // 监听 stdin 可读int rc = select(maxfd + 1, &rfds, NULL, NULL, &tv);if (rc < 0) {if (errno == EINTR) continue; // 被信号中断,重来perror("select");break;}// 1) 先处理 I/Oif (rc > 0 && FD_ISSET(0, &rfds)) {char buf[256];ssize_t nr = read(0, buf, sizeof(buf));if (nr > 0) {write(1, "read stdin\n", 11);}}// 2) 再处理定时(可能一次到期多个,视设计而定)long long now = now_ms();if (now >= next_expire) {// 触发“定时回调”write(1, "timer fired\n", 12);// 重新安排下一次next_expire = now + interval;}}return 0;
}

若用 poll(2),思路一致:把 remain 毫秒放进 poll(timeout_ms),返回后同样先处理 I/O,再检查到期。区别仅在 API 形式,这里就不重复放代码了。


方案 B:用 timerfd + epoll 把定时纳入 I/O 事件流

要点:创建 timerfd(推荐 CLOCK_MONOTONIC),把它加入 epoll;设置一次性到期(one-shot);到期时 timerfd 变为可读,在读回 8 字节计数后执行回调,然后重新设置下一次到期。这样,代码路径与普通套接字 I/O 完全一致。

// build: gcc -Wall -O2 epoll_timerfd.c -o epoll_timerfd
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <sys/timerfd.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <fcntl.h>static int set_nonblock(int fd) {int fl = fcntl(fd, F_GETFL, 0);if (fl < 0) return -1;return fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}static void arm_timerfd(int tfd, int ms) {struct itimerspec its;memset(&its, 0, sizeof(its));// one-shot:只设置 it_value,不设置 it_intervalits.it_value.tv_sec  = ms / 1000;its.it_value.tv_nsec = (ms % 1000) * 1000000;if (timerfd_settime(tfd, 0, &its, NULL) < 0) {perror("timerfd_settime");exit(1);}
}int main(void) {// 1) 创建 epollint ep = epoll_create1(EPOLL_CLOEXEC);if (ep < 0) { perror("epoll_create1"); return 1; }// 2) 创建 timerfd(MONOTONIC 避免系统时间跳变影响)int tfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);if (tfd < 0) { perror("timerfd_create"); return 1; }// 3) 把 timerfd 加入 epollstruct epoll_event ev; memset(&ev, 0, sizeof(ev));ev.events = EPOLLIN; // 也可用 EPOLLET,看你整体风格ev.data.u32 = 1;     // 简单标识if (epoll_ctl(ep, EPOLL_CTL_ADD, tfd, &ev) < 0) {perror("epoll_ctl add timerfd"); return 1;}// 4) 也把 stdin(0) 放进来,演示 I/O & 定时“同路由”int stdin_fd = 0;set_nonblock(stdin_fd);memset(&ev, 0, sizeof(ev));ev.events = EPOLLIN;ev.data.u32 = 2;if (epoll_ctl(ep, EPOLL_CTL_ADD, stdin_fd, &ev) < 0) {perror("epoll_ctl add stdin"); return 1;}// 5) 设定首次 1000ms 后触发int interval_ms = 1000;arm_timerfd(tfd, interval_ms);// 6) 事件循环struct epoll_event events[16];for (;;) {int n = epoll_wait(ep, events, 16, -1);if (n < 0) {if (errno == EINTR) continue;perror("epoll_wait"); break;}for (int i = 0; i < n; ++i) {if (events[i].data.u32 == 1 && (events[i].events & EPOLLIN)) {// timerfd 就绪:必须 read 8 字节计数uint64_t cnt;ssize_t r = read(tfd, &cnt, sizeof(cnt));if (r == sizeof(cnt)) {// 触发“定时回调”write(1, "timer fired\n", 12);// 重新安排下一次(one-shot 设计)arm_timerfd(tfd, interval_ms);} else if (r < 0 && errno == EAGAIN) {// 非阻塞读完} else {perror("read timerfd");}} else if (events[i].data.u32 == 2 && (events[i].events & EPOLLIN)) {// stdin 可读:与 socket/pipe 一样处理char buf[256];ssize_t nr = read(0, buf, sizeof(buf));if (nr > 0) {write(1, "read stdin\n", 11);}}}}close(tfd);close(ep);return 0;
}

对照要点(便于你在工程中选型)

  • 一致性

    • select/poll 超时:定时靠“返回值 + 当前时间比较”,I/O 走 fd 事件 → 两条路径。
    • timerfd:定时也是一个 fd,与 I/O 完全“同路径”。
  • 易错点

    • select/poll:计算剩余时间与“更新最早到期”的竞态需要谨慎处理。
    • timerfd:记得read 出 8 字节计数清空可读状态;建议用 one-shot,回调后再 settime
  • 推荐时钟CLOCK_MONOTONIC(避免系统时间调整导致跳变)。

http://www.dtcms.com/a/490720.html

相关文章:

  • 百度网盘怎么实现不限速的高速下载?
  • UltraEdit做网站教程定制开发网站如何报价单
  • 《彻底理解C语言指针全攻略(5)--指针和函数专题》
  • 广州做网站找哪个公司好建设网络道德教育网站不包括
  • TUP及ESOP动态股权激励:算法是核心
  • 数据库知识全解析:从基础概念到MySQL实战
  • Python - 100天从新手到大师:第五十七天获取网络资源及解析HTML页面
  • PLY文件格式讲解与可视化展现方式(基于Viser库)
  • 中山百度网站排名织梦播放器网站
  • 珠海建站网站兰州模板网站seo价格
  • jQuery面试题精选:从基础到高级
  • 计算机操作系统:死锁概述
  • C++之理解共用体
  • Java Spring配置
  • 【多进线程】python多进线程与通信
  • 低代码新建表单实操:纯表单 / 列表表单配置 + 表名避坑
  • 前端做数据表格的网站网站建设与运营市场风险
  • 从入门到精通:深度探索RT-Thread物联网操作系统
  • GPUStack:开源GPU集群管理工具,解锁AI模型高效运行新可能
  • LeetCode算法日记 - Day 74: 按摩师、打家劫舍II
  • centos离线包获取-附centos7主流离线包资源
  • 电子商务网站建设哪好网站内链建设
  • 网站建设的主题软媒win7优化大师
  • 人力网站建设的建议软文平台发布
  • 【35】MFC入门到精通——MFC运行 不显示对话框 MFC界面不显示
  • 开源 C++ QT QML 开发(二十一)多媒体--视频播放
  • PMBT2222A,215 开关晶体管功率二极管 NXP安世半导体 音频放大电路 LED驱动 应用
  • 大语言模型(LLM)入门笔记:嵌入向量与位置信息
  • 网站设计济南做网站的一定要开80或8080端口
  • 【Spring Boot从入门到精通】原理、实战与最佳实践