大家好,我是小康。
朋友们,今天要跟大家聊个让无数程序员头疼的话题——Linux定时器。别看这玩意儿平时不起眼,但真要用起来,坑多得你想哭😭

一、写在前面的话
你有没有遇到过这样的场景?
写个网络程序,需要定期发送心跳包做个游戏服务器,要每秒更新玩家状态搞个监控系统,定时检查服务是否正常甚至只是想让程序延时几秒再执行某个操作
如果你点头了,那恭喜你——定时器绝对是你绕不开的技能点!
我记得刚开始写Linux程序的时候,遇到需要定时执行任务的场景,第一反应就是Google一下"Linux定时器怎么用"。结果搜出来一堆alarm()、setitimer()、timerfd_create()...看得我一头雾水。
到底该用哪个?它们有什么区别?为什么有这么多种定时器?
相信很多小伙伴都有过同样的困惑。今天咱们就来彻底搞懂Linux定时器的前世今生,保证看完之后你也能成为定时器专家!
二、第一代:古老而经典的alarm()
1. 最简单的开始
话说回来,Linux最早的定时器就是alarm(),简单到爆:
复制
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
void timeout_handler(int sig) {
printf("时间到!该起床搬砖了!\n");
}
int main() {
signal(SIGALRM, timeout_handler);
alarm(5); // 5秒后触发
pause(); // 等待信号
return 0;
}1.2.3.4.5.6.7.8.9.10.11.12.13.14.
看起来挺简单的对吧? 但是兄弟,这里面的坑可不少:
只能精确到秒 - 你想要毫秒级定时?不好意思,做不到全局只能有一个 - 你在一个地方调用了alarm(10),另一个地方又调用alarm(5),前面那个就被覆盖了容易被系统调用中断 - sleep()、read()这些函数被SIGALRM打断后会提前返回2. 真实踩坑经历
我当年就因为不知道alarm()是全局唯一的,在一个多模块的项目里用了好几个alarm(),结果定时器莫名其妙地不按预期工作。调试了好久才发现是被互相覆盖了。
三、第二代:更灵活的setitimer()
1. 进步在哪里?
既然alarm()这么局限,Linux就推出了升级版——setitimer():
复制
#include <sys/time.h>
#include <signal.h>
#include <stdio.h>
void timer_handler(int sig) {
staticint count = 0;
printf("第%d次定时触发!\n", ++count);
}
int main() {
struct itimerval timer;
signal(SIGALRM, timer_handler);
// 设置定时器:1秒后开始,每0.5秒触发一次
timer.it_value.tv_sec = 1; // 首次触发时间
timer.it_value.tv_usec = 0;
timer.it_interval.tv_sec = 0; // 重复间隔
timer.it_interval.tv_usec = 500000; // 0.5秒 = 500000微秒
setitimer(ITIMER_REAL, &timer, NULL);
while(1) {
pause(); // 等待信号
}
return 0;
}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.
这就厉害多了!
支持微秒级精度可以设置周期性触发有三种定时器类型(REAL、VIRTUAL、PROF)2. 但是...新的问题来了
虽然setitimer()比alarm()强大,但还是有些让人头疼的地方:
还是基于信号 - 信号处理的那些坑一个都没少每个进程还是只能有一个ITIMER_REAL - 多个定时器?也不支持信号可能丢失 - 在信号处理函数执行期间,新的信号可能被丢弃
四、第三代:专业级的POSIX定时器
1. 更加专业的选择
在timerfd出现之前,还有一个重要的过渡产品——POSIX定时器(timer_create系列)。这玩意儿是POSIX标准定义的,比setitimer()更专业,但又没有timerfd()那么现代化。
复制
#include <time.h>
#include <signal.h>
#include <stdio.h>
timer_t timerid;
int timer_count = 0;
void timer_handler(int sig, siginfo_t *si, void *uc) {
timer_t *tidp = si->si_value.sival_ptr;
printf("第%d次POSIX定时器触发!timer_id: %p\n", ++timer_count, tidp);
}
int main() {
struct sigevent sev;
struct itimerspec its;
struct sigaction sa;
// 设置信号处理函数
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = timer_handler;
sigemptyset(&sa.sa_mask);
sigaction(SIGUSR1, &sa, NULL);
// 创建定时器
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = SIGUSR1;
sev.sigev_value.sival_ptr = &timerid;
if (timer_create(CLOCK_REALTIME, &sev, &timerid) == -1) {
perror("timer_create failed");
return-1;
}
// 设置定时器参数:1秒后开始,每500ms触发一次
its.it_value.tv_sec = 1;
its.it_value.tv_nsec = 0;
its.it_interval.tv_sec = 0;
its.it_interval.tv_nsec = 500000000; // 500ms
timer_settime(timerid, 0, &its, NULL);
printf("POSIX定时器启动,按Ctrl+C退出\n");
while (1) {
pause();
}
timer_delete(timerid);
return 0;
}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.
看起来是不是比setitimer()复杂多了? 但功能也更强大:
2. POSIX定时器的优势支持多个定时器 - 终于可以创建多个了!每个都有独立的timer_t标识纳秒级精度 - 和timerfd一样精确灵活的通知方式 - 不仅可以发信号,还可以创建线程或者什么都不做更好的信息传递 - 可以通过siginfo_t传递额外信息3. 三种通知方式
POSIX定时器最酷的地方是支持三种通知方式:
(1) 信号通知(最常用)
复制
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = SIGUSR1;1.2.
(2) 线程通知(高级用法)
复制
sev.sigev_notify = SIGEV_THREAD;
sev.sigev_notify_function = thread_handler;
sev.sigev_notify_attributes = NULL;1.2.3.
(3) 无通知(轮询模式)
复制
sev.sigev_notify = SIGEV_NONE;
// 然后用timer_gettime()主动查询1.2.
4. 我的使用心得
POSIX定时器我在一个服务器监控项目中用过,需要同时监控多个不同的指标,每个指标的检查频率都不一样。用setitimer()根本搞不定,但POSIX定时器就很合适:
复制
timer_t cpu_timer, memory_timer, disk_timer, network_timer;
// CPU使用率:每秒检查一次
create_posix_timer(&cpu_timer, SIGUSR1, 1000);
// 内存使用率:每30秒检查一次
create_posix_timer(&memory_timer, SIGUSR2, 300000);
// 磁盘IO:每分钟检查一次
create_posix_timer(&disk_timer, SIGRTMIN, 600000);
// 网络连接:每分钟检查一次
create_posix_timer(&network_timer, SIGRTMIN+1, 600000);1.2.3.4.5.6.7.8.9.10.11.12.13.
这样每个监控任务都有自己独立的定时器,互不干扰,代码逻辑也很清晰。
但是...POSIX定时器也有它的问题:
还是基于信号 - 信号处理的坑一个都没少代码复杂 - 比alarm()和setitimer()复杂多了移植性问题 - 有些老系统支持不够好
所以虽然功能强大,但在现代Linux开发中,大家更倾向于直接用timerfd。
五、第四代:现代化的timerfd
1. 革命性的改变
到了Linux 2.6.25,终于迎来了真正的现代化定时器——timerfd!
这东西彻底改变了游戏规则:把定时器变成了文件描述符!
复制
#include <sys/timerfd.h>
#include <unistd.h>
#include <stdio.h>
#include <stdint.h>
int main() {
int timer_fd;
struct itimerspec timer_spec;
uint64_t expirations;
// 创建定时器文件描述符
timer_fd = timerfd_create(CLOCK_REALTIME, 0);
if (timer_fd == -1) {
perror("timerfd_create failed");
return-1;
}
// 设置定时器:2秒后开始,每1秒触发一次
timer_spec.it_value.tv_sec = 2;
timer_spec.it_value.tv_nsec = 0;
timer_spec.it_interval.tv_sec = 1;
timer_spec.it_interval.tv_nsec = 0;
timerfd_settime(timer_fd, 0, &timer_spec, NULL);
printf("定时器启动,等待触发...\n");
for (int i = 0; i < 5; i++) {
// 就像读文件一样读取定时器
ssize_t bytes = read(timer_fd, &expirations, sizeof(expirations));
if (bytes == sizeof(expirations)) {
printf("定时器触发了%llu次\n", expirations);
}
}
close(timer_fd);
return 0;
}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.
这简直是质的飞跃!
2. 为什么timerfd这么香?文件描述符 - 可以用select()、poll()、epoll()监听,完美融入事件循环纳秒级精度 - 想要多精确有多精确无限个定时器 - 想创建多少个就创建多少个不依赖信号 - 再也不用担心信号处理的各种坑更好的并发支持 - 在事件驱动的程序中表现出色3. 配合epoll使用更香
复制
#include <stdio.h>
#include <unistd.h>
#include <sys/timerfd.h>
#include <sys/epoll.h>
#include <stdint.h>
int main() {
int timerfd1, timerfd2, epollfd;
struct itimerspec its;
struct epoll_event ev, events[10];
uint64_texp;
// 创建两个定时器
timerfd1 = timerfd_create(CLOCK_REALTIME, 0);
timerfd2 = timerfd_create(CLOCK_REALTIME, 0);
// 创建epoll实例
epollfd = epoll_create1(0);
// 将定时器加入epoll监听
ev.events = EPOLLIN;
ev.data.fd = timerfd1;
epoll_ctl(epollfd, EPOLL_CTL_ADD, timerfd1, &ev);
ev.data.fd = timerfd2;
epoll_ctl(epollfd, EPOLL_CTL_ADD, timerfd2, &ev);
// 设置定时器1:每1秒触发
its.it_value.tv_sec = 1;
its.it_value.tv_nsec = 0;
its.it_interval.tv_sec = 1;
its.it_interval.tv_nsec = 0;
timerfd_settime(timerfd1, 0, &its, NULL);
// 设置定时器2:每2秒触发
its.it_value.tv_sec = 2;
its.it_interval.tv_sec = 2;
timerfd_settime(timerfd2, 0, &its, NULL);
printf("高性能定时器系统启动!\n");
while (1) {
int nfds = epoll_wait(epollfd, events, 10, -1);
for (int n = 0; n < nfds; n++) {
int fd = events[n].data.fd;
read(fd, &exp, sizeof(uint64_t));
if (fd == timerfd1) {
printf("⚡ 快速定时器触发 (1秒间隔)\n");
} elseif (fd == timerfd2) {
printf("🐌 慢速定时器触发 (2秒间隔)\n");
}
}
}
return 0;
}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.
这就是现代Linux程序的标准写法! 事件驱动,高性能,代码还清晰易懂。
六、实际项目中该选哪个?
1. 快速决策指南
如果你只是想要个简单的定时:
复制
alarm(5); // 够用了,别想太多1.
如果需要周期性定时,而且精度要求不高:
复制
setitimer(ITIMER_REAL, &timer, NULL); // 经典选择1.
如果需要多个定时器,但不想用太新的API:
复制
timer_create() + timer_settime(); // POSIX标准,兼容性好1.
如果是现代项目,特别是网络服务器:
复制
timerfd_create() + epoll(); // 这就对了!1.
2. 性能对比
我之前做过一个简单的功能测试,看看各种定时器的支持能力:
alarm(): 全局只能有1个,新的会覆盖旧的setitimer(): 每种类型只能1个(REAL、VIRTUAL、PROF),最多3个POSIX定时器: 支持多个,具体数量受系统限制(通常几百个),但信号处理开销较大timerfd(): 支持多个,数量主要受文件描述符限制
实际项目中的选择建议:
如果只需要1-2个定时器:setitimer()够用如果需要多个定时器:POSIX定时器和timerfd都可以,但timerfd在事件驱动程序中更高效如果是高并发网络程序:timerfd() + epoll()性能最好,因为可以和其他I/O事件统一处理3. 兼容性考虑alarm()/setitimer(): 几乎所有Unix系统都支持POSIX定时器: 理论上是POSIX标准,但实际支持情况复杂:Linux 2.6+原生支持(但可能需要链接 -lrt),macOS/BSD支持有限,Windows需要通过Cygwin等兼容层timerfd(): Linux 2.6.25+专有,其他系统不支持
实际上,跨平台的定时器API是一个普遍难题,每个操作系统都有自己的实现方式。如果你的项目需要真正的跨平台,可能需要:
使用第三方库(如libuv、libevent)或者针对不同平台编写不同的实现
七、进阶技巧分享
1. 高精度定时器
想要更高的精度?试试CLOCK_MONOTONIC:
复制
timer_fd = timerfd_create(CLOCK_MONOTONIC, 0);1.
CLOCK_MONOTONIC不受系统时间调整影响,更适合做精确的间隔定时。
2. 一次性定时器
有时候你只想要一个一次性的延时:
复制
timer_spec.it_value.tv_sec = 5; // 5秒后触发
timer_spec.it_value.tv_nsec = 0;
timer_spec.it_interval.tv_sec = 0; // 不重复
timer_spec.it_interval.tv_nsec = 0;1.2.3.4.
3. 定时器管理器
在复杂项目中,你可能需要管理很多定时器。我一般会封装一个定时器管理器:
复制
typedef struct {
int fd;
void (*callback)(void *data);
void *data;
} Timer;
// 创建定时器
Timer* create_timer(int interval_ms, void (*callback)(void*), void *data);
// 删除定时器
void destroy_timer(Timer *timer);
// 在主事件循环中处理定时器事件
void handle_timer_event(Timer *timer);1.2.3.4.5.6.7.8.9.10.11.12.
这样管理起来就清爽多了。
八、总结:定时器进化的启示
从alarm()到timerfd(),Linux定时器的进化史其实反映了整个系统编程的发展趋势:
从简单到复杂 - 功能越来越强大从单一到多元 - 支持更多使用场景从同步到异步 - 更好地融入事件驱动架构从信号到文件描述符 - 统一的编程模型
如果你是新手,建议从alarm()开始理解基本概念,了解一下POSIX定时器的功能特性,然后直接跳到timerfd()学习现代用法。
如果你是老手,是时候把那些老旧的alarm()和setitimer()代码重构了。如果项目只在Linux上运行,直接用timerfd();如果需要跨平台,考虑使用成熟的第三方库。
选择建议总结:
学习路径: alarm() → POSIX定时器概念 → timerfd()实践跨平台项目: 使用libuv、libevent等成熟库,别自己造轮子Linux专项目: 直接用timerfd() + epoll()简单脚本: alarm()够用,别过度设计