在 定时器 这一节中,我们提及到TimerEvent的实现。本质上,它是通过epoll()或select()中阻塞的超时参数来实现定时任务的。TimerEvent在实际运用中的会有约400us的误差。如果想要精度更好的定时器,则需要使用本节介绍的TimerFd。
TimerFd 定义在 eventx/timer_fd.h 中,是基于Linux内核提供的timer_fd实现的。具体的原理可阅读相关文章《Linux fd 系列 — 定时器 timerfd 是什么?》
本节通过一个示例来引导大家掌握TimerFd的使用方法。
- 与
TimerEvent不同的是TimerFd不属于Loop的一部分,也没有被纳入到框架。要使用它,都需要#include <tbox/eventx/timer_fd.h> - 在
MyModule中分别实列化两个TimerFd对象:oneshot_timer_与repeat_timer_; - 在
MyModule的初始化列表中对oneshot_timer_与repeat_timer_进行构造。注意,它的初始化需要ctx().loop()获取的Loop对象指针; - 在
onInit()中对它们进行初始化,只传一个时间的表示单次触发;传两个时间的表示单次触发之后,还需要重复触发; - 在
onStart()中使能它们。在工程中,不是一定要在onStart()中使能它们,根据业务需要我们也可以在需要的时间再使能它们; - 在
onStop()中关闭它们; - 在
onCleanup()中清理它们;
编译后,执行效果:

我们注意到,它的定时精确误差在5us以内,非常可观。
思考:为什么使用TimerFd的精准度会比TimerEvent高这么多?
猜想:由于TimerFd是基于Linux内核中的timer_fd实现的。在timer_fd触发超时时,内核立即让timer_fd的文件描述符变成可读,鉴于定时器的实时性要求,Linux内核会立即唤醒对应的线程并执行。而基于epoll()与select()超时参数的TimerEvent则得不到Linux内核的特殊照顾。当epoll()或select()等待超时后,Linux内核只将对应的线程设置为READY状态并排队等待CPU资源,以致于执行的时间点存在较大的误差。
问题:TimerEvent,TimerPool,TimerFd 这三种实现方式如何抉择?
回答:
- 对于日常对精度要求不太高(如1ms精度可接受)的定时执行场景,可从
TimerEvent与TimerPool中任选一种。如果是对精度要求高,要保证100us以内误差的,使用TimerFd; - 遇到首次触发与后续触发间隔不一致的,采用
TimerFd。
