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

Linux软件定时器回顾

主要实现方式

1. setitimer() + 信号 

这是传统的POSIX方法:

struct itimerval
{/* Value to put into `it_value' when the timer expires. */struct timeval it_interval;/* Time to the next timer expiration. */struct timeval it_value;
};it_interval:计时器的初始值,一般基于这个初始值来加或者来减,看控制函数的参数配置
it_value:程序跑到这之后,多久启动定时器//结构体
struct timeval
{__time_t tv_sec; /* Seconds. */__suseconds_t tv_usec; /* Microseconds. */
};//函数
int setitimer (__itimer_which_t __which,const struct itimerval *__restrict __new,struct itimerval *__restrict __old)/*setitimer()将value指向的结构体设为计时器的当前值,如果ovalue不是NULL,将返回计时器原有值。
which:三种类型
ITIMER_REAL //数值为0,计时器的值实时递减,发送的信号是SIGALRM。
ITIMER_VIRTUAL //数值为1,进程执行时递减计时器的值,发送的信号是SIGVTALRM。
ITIMER_PROF //数值为2,进程和系统执行时都递减计时器的值,发送的信号是SIGPROF。
很明显,这边需要捕获对应的信号进行逻辑相关处理 signal(SIGALRM,signal_handler);
返回说明:
成功执行时,返回0。失败返回-1*/
#include <sys/time.h>
#include <signal.h>// 设置定时器
struct itimerval timer;
timer.it_value.tv_sec = 1;     // 首次触发时间
timer.it_value.tv_usec = 0;
timer.it_interval.tv_sec = 1;  // 重复间隔
timer.it_interval.tv_usec = 0;setitimer(ITIMER_REAL, &timer, NULL);// 信号处理函数
void timer_handler(int sig) {// 处理定时任务
}signal(SIGALRM, timer_handler);

特点:

  • 使用 ITIMER_REAL 产生 SIGALRM 信号
  • 精度可达微秒级
  • 受信号处理机制限制(异步信号安全)

2. timerfd (现代推荐方法)

struct itimerspec {struct timespec it_interval;  // 定时器周期(0 表示单次触发)struct timespec it_value;     // 首次到期时间(0 表示停止定时器)
};it_value:首次触发的延迟时间。
it_interval:周期性触发的间隔时间。如果为 0,则为单次定时器。timerfd_create(int clockid, int flags)
/*
clockid:指定定时器所基于的时钟源。常用值有:
CLOCK_REALTIME:系统实时时间(可被 NTP 调整)。
CLOCK_MONOTONIC:单调递增时间(不受系统时间调整影响,推荐用于定时)。
flags:可选标志位,如 TFD_CLOEXEC(执行 exec 时自动关闭)、TFD_NONBLOCK(非阻塞模式)。
*/timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value)
/*
fd:由 timerfd_create 返回的文件描述符。
flags:标志位。0 表示 new_value->it_value 是相对时间;TFD_TIMER_ABSTIME 表示是绝对时间。
new_value:指向 itimerspec 结构体,指定新的定时器值。
old_value:可选,用于返回之前的定时器设置。
*/timerfd_gettime(int fd, struct itimerspec *curr_value)
/*
fd:timerfd 文件描述符。
curr_value:指向 itimerspec 结构体,用于接收当前值。
*/
#include <sys/timerfd.h>int timerfd = timerfd_create(CLOCK_REALTIME, 0);struct itimerspec timer;
timer.it_value.tv_sec = 1;
timer.it_value.tv_nsec = 0;
timer.it_interval.tv_sec = 1;
timer.it_interval.tv_nsec = 0;timerfd_settime(timerfd, 0, &timer, NULL);// 在epoll/select中监听
uint64_t exp;
read(timerfd, &exp, sizeof(exp));

优势:

  • 基于文件描述符,可与epoll集成
  • 避免信号处理的复杂性
  • 更精确的控制

3. timer_create() (POSIX定时器)

#include <signal.h>
#include <time.h>timer_t timerid;
struct sigevent sev;
struct itimerspec timer;// 创建定时器
sev.sigev_notify = SIGEV_THREAD;
sev.sigev_notify_function = timer_callback;
timer_create(CLOCK_REALTIME, &sev, &timerid);// 启动定时器
timer_settime(timerid, 0, &timer, NULL);

关键要点

  1. 信号安全:在信号处理函数中只能调用异步信号安全的函数
  2. 精度限制:受系统时钟分辨率影响(通常1-10ms)
  3. 线程安全:现代应用推荐使用timerfd避免信号竞争
  4. 多种时钟源CLOCK_REALTIMECLOCK_MONOTONIC

工作流程

  1. 调用 timerfd_create 创建一个 timerfd,得到一个文件描述符 fd
  2. 设置 itimerspec 结构体,定义定时器的首次触发时间和周期。
  3. 调用 timerfd_settime(fd, 0, &timer_spec, NULL) 启动定时器。
  4. 在事件循环中(如 epoll_wait),监控该 fd 的可读事件。
  5. 当定时器到期时,fd 变为可读。读取该 fd(通常读取 8 字节)可以获取到期次数,并清除可读状态。
  6. 可以再次调用 timerfd_settime 来修改或重启定时器。
#include <sys/timerfd.h>
#include <time.h>
#include <unistd.h>
#include <stdint.h>
#include <stdio.h>int main() {// 1. 创建 timerfdint tfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);if (tfd == -1) {perror("timerfd_create");return 1;}// 2. 设置定时器:2秒后首次触发,之后每1秒触发一次struct itimerspec timer_spec;timer_spec.it_value.tv_sec = 2;   // 首次延迟 2 秒timer_spec.it_value.tv_nsec = 0;timer_spec.it_interval.tv_sec = 1; // 周期 1 秒timer_spec.it_interval.tv_nsec = 0;if (timerfd_settime(tfd, 0, &timer_spec, NULL) == -1) {perror("timerfd_settime");close(tfd);return 1;}// 3. 模拟事件循环(这里简化为直接 read)uint64_t expirations;while (1) {// read 会阻塞直到定时器到期ssize_t s = read(tfd, &expirations, sizeof(uint64_t));if (s != sizeof(uint64_t)) {// 处理错误break;}printf("Timer expired %lu times\n", expirations);}close(tfd);return 0;
}

setitimer()和timerfd的优缺点对比是什么?

setitimer()

优点:

  1. 简单易用:API 简单,只需要设置 struct itimerval 并调用 setitimer()
  2. POSIX 标准:跨平台兼容性较好(在支持 POSIX 的系统上)。
  3. 微秒级精度:支持微秒(μs)级别的定时精度。
  4. 轻量级:不需要额外的系统调用或复杂的设置。

缺点:

  1. 信号依赖:依赖信号(如 SIGALRM)进行通知,信号处理复杂且容易出错。
  2. 异步信号安全限制:在信号处理函数中只能调用异步信号安全的函数(如 write()sigprocmask()),不能调用 printf()malloc() 等。
  3. 单一定时器限制:每个进程只能有 三个 setitimer 定时器(ITIMER_REALITIMER_VIRTUALITIMER_PROF),无法创建多个独立定时器。
  4. 难以集成 I/O 多路复用:信号不是文件描述符,无法直接用于 select()poll()epoll() 等事件循环。
  5. 竞态条件:信号可能被阻塞或丢失,处理不当会导致定时不准或逻辑错误。

适用场景:简单的单一定时任务,如心跳检测、超时控制等。


timerfd

优点:

  1. 基于文件描述符:返回一个文件描述符(fd),可以无缝集成到 epollpollselect 等 I/O 多路复用机制中,非常适合事件驱动架构(如 Reactor 模式)。
  2. 无信号干扰:不依赖信号,避免了信号处理的复杂性和安全问题。
  3. 支持多个定时器:每个 timerfd_create() 调用创建一个独立的定时器,可创建多个并发定时器。
  4. 高精度和灵活性:支持纳秒级精度(struct itimerspec),可设置绝对或相对时间,支持单次和周期性触发。
  5. 线程安全:多个线程可以安全地读取 timerfd,适合多线程程序。
  6. 可读性高:通过 read() 读取超时次数,逻辑清晰,易于调试。

缺点:

  1. Linux 特有timerfd 是 Linux 特有的系统调用,不具备跨平台性。
  2. 稍微复杂:相比 setitimer(),需要更多的设置步骤(创建、设置时间、读取等)。
  3. 需要处理读取:每次定时器到期后需要 read() 一次,否则可能会累积事件。

适用场景:高性能服务器、事件驱动程序、需要多个定时器的复杂应用。


对比总结表

特性setitimer()timerfd
通知机制信号(SIGALRM)文件描述符(可 epoll)
定时器数量最多 3 个任意多个
精度微秒级纳秒级
跨平台性POSIX 兼容Linux 特有
I/O 多路复用集成不支持支持(epoll/poll/select)
信号安全问题
复杂度简单中等
适用场景简单定时任务高性能、事件驱动系统

结论

  • 如果你写的是 简单的脚本或工具,且只需要一个定时器,setitimer() 足够用。
  • 如果你写的是 高性能服务器、网络程序或事件驱动框架(如 Redis、Nginx 风格),强烈推荐使用 timerfd,因为它更现代、更安全、更灵活。

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

相关文章:

  • 本地部署开源媒体服务器 Komga 并实现外部访问( Windows 版本)
  • 容器存储驱动升级:美国VPS文件系统优化全指南
  • 上海我店模式的多维度探究
  • 对于STM32工程模板
  • CRM、ERP、HRP系统有啥区别?
  • 250830-Docker从Rootless到Rootful的Gitlab镜像迁移
  • 深刻理解软硬件链接
  • ubuntu24.04 qt6安装
  • 学习游戏制作记录(各种优化)
  • 复制VMware虚拟机后的网络配置
  • leetcode算法刷题的第二十二天
  • 论《运动战》
  • Linux查看有线网卡和无线网卡详解
  • UNet改进(36):融合FSATFusion的医学图像分割
  • Vue基础知识-单向绑定v-bind、双向绑定v-model、插值语法{{}}、Object.defineProperty实现数据代理
  • PostgreSQL数据类型一览(数值类型)
  • Spring和mybatis整合后事务拦截器TransactionInterceptor开启提交事务流程
  • 【Java实战⑧】Java常用类实战:解锁String、Object与包装类的奥秘
  • STL中的容器,迭代器
  • 规律作息 + 养成好的习惯 + 考研倒计时 111 天 + 线面积分入门 1 下半部分
  • 【路由器】TP Link 路由器为何无法进入管理后台
  • HarmonyOS AppStorage:跨组件状态管理的高效解决方案
  • 2025年06月 Scratch 图形化(二级)真题解析#中国电子学会#全国青少年软件编程等级考试
  • 大模型训练中的 logits 是什么
  • npm基础
  • SNMPv3开发--snmpd.conf
  • Vue加载速度优化,verder.js和element.js加载速度慢解决方法
  • VGG改进(6):基于PyTorch的VGG16-SE网络实战
  • 项目管理方法适用场景对比
  • Linux kernel arm64 启动流程