如何使用C++11实现跨平台的定时器timer

2025-04-17 05:08:04
推荐回答(1个)
回答1:

一个Timer的实现需要具备以下几个行为:

StartTimer(Interval, ExpiryAction)

注册一个时间间隔为 Interval 后执行 ExpiryAction 的定时器实例,其中,返回 TimerId 以区分在定时器系统中的其他定时器实例。

StopTimer(TimerId)

根据 TimerId 找到注册的定时器实例并执行 Stop 。

PerTickBookkeeping()

在一个 Tick 时间粒度内,定时器系统需要执行的动作,它最主要的行为,就是检查定时器系统中,是否有定时器实例已经到期。

具体的代码实现思路就是:

StartTimer的时候,把 当前时间 + Interval
作为key放入一个容器,然后在Loop的每次Tick里,从容器里面选出一个最小的key与当前时间比较,如果key小于当前时间,则这个key代表的
timer就是expired,需要执行它的ExpiryAction(一般为回调)。

这里有两个实现的细节:

获取当前时间

包含时间精度,使用系统时间还是CPU时间(asio里的deadline_timer和steady_timer的区别)
常用的API是:
Windows: QueryPerformanceFrequency() 和 QueryPerformanceCounter()
Linux: clock_gettime()
OSX: gettimeofday()或者mach_absolute_time()
当然在C++11里也可以偷懒使用chrono的high_resolution_clock std::chrono::high_resolution_clock

2.timer容器的选择

容器应该能够在很短的时间内找到MinValue
最小堆的find-min复杂度是O(1),所以蛮受人喜欢的
STL里提供有堆的API,make_heap, push_heap, pop_heap, sort_heap

3. PerTickBookkeeping是放在主循环线程还是另起线程

另起线程需要做好线程间通信,asio和skynet有单独的timer线程

一些代码实现:

这是boost.asio的实现的timer_queue,用的是最小堆
asio/timer_queue.hpp at master · chriskohlhoff/asio · GitHub

这是libuv的timer,采用的是红黑树实现(windows),linux下还是最小堆
libuv/timer.c at v1.x · libuv/libuv · GitHub

这是云风的skynet timer实现,采用链表实现
skynet/skynet_timer.c at master · cloudwu/skynet · GitHub