java实现时间轮定时器_基于侵入式链表的时间轮定时器实现

本文介绍了一种基于C++的游戏服务器定时器实现方案,利用时间轮和侵入式链表来达到秒级精度,适用于游戏排行结算和比赛通知等场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

游戏服务器一般都会需要这样一种功能 – 在某个确定的时间点干某件特定的事情,例如:某个排行需要在每月第一天进行结算,某个比赛需要在下周六中午12:00准时通知服务器所有玩家比赛开始……当然,这种功能不局限于游戏服务器,其他类型服务器或多或少也有包含。我们称之这个功能为定时器功能或者定时器组件。

这里实现了一种用C++模板机制实现的秒级(比较容易改写成毫秒级)定时器,核心算法使用的是时间轮(Timing-Wheel),辅助数据结构使用的是侵入式链表(Intrusive-List)。首先简单介绍一些基本概念。

侵入式链表(Intrusive-List)

最典型的Intrusive-List例子应该就是linux内核中使用的链表了,它的优点是用户知道自己在表中的位置,删除比较方便,且用户自己管理数据块,这个数据块可以被多个链表同时共享(注意这是一个很重要的特性,相比非侵入式链表节约了不少空间),这种数据结构在redis,nginx代码中广泛使用。

相对应的,非侵入式链表(Non-intrusive-list)的例子就是std::list了,被使用在各大软件与应用中,易用,稳定,用户群里大基本任何关于它的问题都可以google到,当然这里说的Non-intrusive-list不单单只是指std::list,只是std::list占去了Non-intrusive-list至少90%的“市场份额”。

一些比较好的关于链表资料:

时间轮(Timing-Wheel)

对于Timing-Wheel的基本理解就像老式钟表,一个圆盘,上面很多刻度,外加刻度针。每次Tick,轮盘转动(根据物理相对运动,也可以理解成刻度针摆动),这时指针指向新的刻度标签,这个刻度标签上有任何过期定时器那么则进行处理,定期进行下一次Tick,处理下一个刻度标签上的事件……

本文的实现

游戏服务器一般来说不需要很精确的计时(端游上需要毫秒级,一些技能的吟唱是需要毫秒级支持的),特别手游服务器一般精确到秒级即可,所以对于Timing-Wheel的N取值为60即可(1分钟60秒,每秒对应一个轮)。我们采用每个轮子上挂接顺序链表的方式进行定时器存储,这样的话在轮询时只用判断表头是否过期即可。

timewheel.png

对于插入行为,我们根据定时器过期时间(expireTm)对N值进行求余hash,从而找到对应的轮,将其插入顺序链表。

timewheel1.png

对于轮的转动,原则上以秒为单位进行指针移动。

问题与解决

问题1:若定时器过期处理函数(func)耗时过长,导致精度下降,如何处理?

建议是在func中不要放过重的逻辑,如果无法避免重逻辑,可以使用线程解决。但是使用线程会使开发同步性的成本就会增加,也会增加维护的复杂度,所以这是一个取舍过程,本文中的实现无法完全避免这个问题。

问题2:一个过期处理函数(func)耗时几秒,已经导致了精确严重下降,如何处理?

本文实现一个简单的追赶机制,我们在处理定时器过期线程(滴答线程)将每次滴答(Tick)时间默认设置为200ms,并且记录当前的时间指针位置(secStep),当发生了一个很耗时的func,我们会发现当前时间(curTm)大于secStep,那么我将secStep++,直到curTm==secStep我们就不做任何移动当前secStep的操作,让其自动随时间移动,这样在1秒的时间内可以大约追赶4秒误差。

问题3:定时器是否线程安全?

是,粗暴的使用线程锁。

问题4:定时器内存管理是怎么样的?

内存会对定时器事件(timerEvent),进行new/delete(可以优化为高效的内存池模式),无需用户进行额外的内存操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值