2人参与 • 2026-08-06 • Linux
面试官:linux下有哪些实现定时器的方法?它们各有什么优缺点?
菜鸟:额,我知道sleep(3)可以延时...
高手:主要有5种方式——sleep/alarm(精度低)、settimer(周期触发)、timerfd(融入epoll)、时间堆(高性能)、时间轮(大量定时器)。选择哪种取决于精度要求和应用场景。
今天我们就沿着这个问题,简单看一下这些实现。
#include <unistd.h>
#include <signal.h>
// sleep:进程休眠指定秒数
sleep(5); // 精度秒级,挂起进程
// alarm:在指定秒数后发送sigalrm信号
void sig_handler(int signo) {
printf("定时器触发!\n");
}
signal(sigalrm, sig_handler);
alarm(3); // 3秒后触发,只触发一次精度只有秒级,且alarm通过信号处理,存在信号丢失和异步安全问题。
#include <sys/time.h> struct itimerval tick; tick.it_value.tv_sec = 0; // 首次触发时间 tick.it_value.tv_usec = 100000; // 100ms后首次触发 tick.it_interval.tv_sec = 0; // 周期触发间隔 tick.it_interval.tv_usec = 100000; // 每100ms触发一次 setitimer(itimer_real, &tick, null); // 同样通过sigalrm信号处理
支持微秒级精度和周期性触发,但仍受限于信号处理机制。
信号最大的问题是难以与epoll/select等i/o多路复用机制配合。信号中断后,你无法确定是哪个fd就绪,还得处理eintr错误。timerfd应运而生——它把定时器变成了一个文件描述符!
#include <sys/timerfd.h> #include <sys/epoll.h> int timerfd = timerfd_create(clock_monotonic, tfd_nonblock); struct itimerspec new_value; new_value.it_value.tv_sec = 1; // 首次1秒后到期 new_value.it_value.tv_nsec = 0; new_value.it_interval.tv_sec = 0; // 之后每500ms到期 new_value.it_interval.tv_nsec = 500000000; timerfd_settime(timerfd, 0, &new_value, null); // 加入epoll统一管理 epoll_ctl(epfd, epoll_ctl_add, timerfd, &ev);
在epoll循环中:
uint64_t exp;
read(timerfd, &exp, sizeof(uint64_t)); // 必须读,否则会重复触发
printf("定时器到期 %llu 次\n", exp);纳秒级精度,完美融入epoll事件循环,不会丢失定时事件(read会返回到期次数)。
讲完应用层,我们从内核源码角度看看定时器是如何实现的。
linux 2.4内核使用时间轮算法管理定时器。内核把未来时间分成多个槽(slot),每个槽挂接一个定时器链表。
[0] -> timer1 -> timer5 [1] -> timer3 [2] -> null ... [511] -> timer2 -> timer4 指针每tick移动一次,时间复杂度o(1)
这种结构在定时器数量很大时效率极高,但精度受tick频率限制(通常1ms到10ms)。
现代linux内核(2.6.16+)引入了高精度定时器hrtimer,基于红黑树实现。
// 内核中定义一个hrtimer struct hrtimer timer; hrtimer_init(&timer, clock_monotonic, hrtimer_mode_rel); timer.function = my_callback; hrtimer_start(&timer, ktime_set(0, 500000000), hrtimer_mode_rel); // 500ms后回调
传统内核依赖固定的时钟中断(如1000hz),每次中断检查定时器。但在空闲时频繁中断会耗电。
tickless内核动态编程硬件定时器,只在最近一个定时器到期时才产生中断。这样cpu可以长时间睡眠,极大节省功耗(手机、笔记本受益明显)。
在高并发场景(如百万长连接),每个连接都可能有定时器。此时红黑树的o(log n)开销也显得昂贵,工业界常用多层级时间轮:
class timerwheel {
// 5个轮子,分别对应1ms, 20ms, 400ms, 8s, 160s
const int wheel_size = 256;
vector<list<timer*>> wheels[5];
void add_timer(timer* t) {
// 根据超时时间选择层级
int level = get_level(t->expire);
int slot = (t->expire >> (level*8)) & 0xff;
wheels[level][slot].push_back(t);
}
void tick() {
// 当前时间推进一个单位
// 如果是第0层的本轮刻度走完,就级联下降上层
}
};nginx、linux内核网络协议栈都使用了多级时间轮来管理大量连接的超时。
| 定时器方案 | 精度 | 性能 | 适用场景 |
|---|---|---|---|
| alarm/sleep | 秒级 | 低 | 简单脚本 |
| setitimer | 微秒 | 中 | 单进程周期任务 |
| timerfd | 纳秒 | 中 | 需集成事件循环的服务 |
| 时间堆(红黑树) | 纳秒 | o(log n) | 定时器数量适中 |
| 时间轮 | 取决于tick | o(1) | 海量定时器(10万+) |
选择建议:
以上就是linux从闹钟到高精度定时器的实现指南的详细内容,更多关于linux实现高精度定时器的资料请关注代码网其它相关文章!
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论