1. 定时接口

  • sleep函数族: sleep,usleep,nanosleep,clock\_nanosleep

特点:有一定的精度,但是会使线程挂起。

  • 信号:alarm,setitimer

特点:采用了信号SIGALRM,由于SIGALRM信号不可靠,会造成超时通知不可靠,而且多线程中处理信号比较复杂。

  • Linux定时接口:timer\_create/timer\_settime

特点:先创建,再设置。创建时可以配置回调;精度在纳秒级别;需要librt库;想要与业务程序配合,需要进行一定的封装,封装成本比较高。


  • 具有定时功能的API: 多路复用。在Linux上的多路复用机制有select/poll/epoll。

特点:poll/epoll是毫秒级的(millisecond),select超时参数是struct timeval,是微秒级的(microsecond)。

epoll的优势很明显,能将定时功能完美的融入已有的event loop里,有着天然的高并发的能力,millisecond级的精度也足够用。

2. 系统时间

  • time 特点:精度太低,不适合精确定时。
  • ftime 特点:毫秒级精度,但是被废弃了。
  • gettimeofday 特点:使用了vdso技术,精度达到微秒级,并且在x86-64平台上该函数的调用不是系统调用(vdso),POSIX.1-2008中也将这个函数废弃了。
  • Time Stamp Counter 使用汇编指定获取时间戳的计数器,精度应该是最高的,效率可能也应该是最高的。

特点: 一条汇编指令rdtscp即可,libco就是优先使用这个方法获取时间的。

  • clock\_gettime 默认是nanosecond 级精度,是系统调用(\_sys\_clock\_gettime()),会有开销。

特点:调用频繁的话,可能造成损失性能。但是Linux 2.6.32后可以指定参数 CLOCK\_REALTIME\_COARSE 和 CLOCK\_MONOTONIC\_COARSE,粗粒度地获取时间,而不需要发生上下文切换。

和gettimeofday()一样也是vdso技术,使用\_COARSE后缀获取的时间,精度是millisecond级。

3. 定时器的设计

从上述的总结,开源实现和别人的总结中多路复用模式可以达到更好的精度和并发。

epoll 每次只能设置一个超时时间,不能满足需求。

① 结合clock\_gettime获取系统时间,然后再设置epoll\_wait的定时时间。

这种设计下需要使用容器保存所有的定时时间,epoll每次配置最快超时的时间为epoll\_wait的定时时间。

需要选择合适的超时时间保存的容器,保证可以用O(1)获取到最小的那个超时时间。

② 结合Timerfd API

Linux内核2.6.25版本中添加的接口,把超时事件触发为文件描述符,超时发生后,文件描述符可读。

这种机制下超时事件成为了普通的IO事件,也可以设置阻塞/非阻塞,timerfd的精度达到了纳秒级。

Note:libevent2.1中也支持了timerfd,特点是:高效,精确。

每一个超时事件都用timerfd\_create()创建对应的fd,放到epoll中统一管理。

每增加一个定时事件,需要增加3个系统调用。文件描述符是稀缺资源。定时器过多会浪费。

libevent使用的方法:每个event loop共享一个timerfd,每次循环事件之前,取出最近一个超时事件的时间,将timerfd设置为这个超时时间。

4. 超时时间的容器

  • 最小堆实现

libevent 使用的这种模式。

  • 时间轮实现

libco 使用的这种模式。

  • 最小堆和时间轮的时间复杂度

Type add exec
list O(1) O(n)
min-heap O(lgn) O(1)
time wheel O(1) O(n)

标签: Linux, C++, epoll, 时间, 定时器, 超时, 定时, timerfd, 精度

相关文章推荐

添加新评论,含*的栏目为必填