惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hacker News: Front Page
P
Palo Alto Networks Blog
T
ThreatConnect
Apple Machine Learning Research
Apple Machine Learning Research
博客园_首页
T
True Tiger Recordings
P
Privacy & Cybersecurity Law Blog
B
Blog
IT之家
IT之家
Last Week in AI
Last Week in AI
F
Full Disclosure
Hacker News: Ask HN
Hacker News: Ask HN
C
Comments on: Blog
Microsoft Azure Blog
Microsoft Azure Blog
C
Cybersecurity and Infrastructure Security Agency CISA
Microsoft Security Blog
Microsoft Security Blog
博客园 - 【当耐特】
N
News and Events Feed by Topic
NISL@THU
NISL@THU
腾讯CDC
雷峰网
雷峰网
Security Latest
Security Latest
李成银的技术随笔
M
Microsoft Research Blog - Microsoft Research
L
LangChain Blog
L
Lohrmann on Cybersecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Check Point Blog
Y
Y Combinator Blog
Recent Announcements
Recent Announcements
博客园 - Franky
N
News | PayPal Newsroom
V
V2EX
A
About on SuperTechFans
The Register - Security
The Register - Security
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google Online Security Blog
Google Online Security Blog
MyScale Blog
MyScale Blog
Cisco Talos Blog
Cisco Talos Blog
Vercel News
Vercel News
WordPress大学
WordPress大学
C
Cyber Attacks, Cyber Crime and Cyber Security
The Hacker News
The Hacker News
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
爱范儿
爱范儿
A
Arctic Wolf
L
LINUX DO - 最新话题
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

博客园 - 世纪末の魔术师

坚毅,是一种缓慢修理自己的方式 语言的边界,与软件的命运 人类与AI协同进化 《坚毅》第一部分读书笔记 用 System.CommandLine 构建工程级 CLI 工具 用 Command 模式构建可扩展的命令行工具 从哎呦”到语言宇宙 ——读《What Is ChatGPT Doing … And Why Does It Work?》 🚫 为什么「定时器」不应该是线程安全的? C# AOT编译后——调用其类库方法因顺序出错? UnitTask中的Forget()与 CTS 光线追踪和球体追踪 八、方法(method) 二十、异常与状态管理(Exception&State Management) 二十八、IO绑定的异步操作(IO-Bound Async) 二十二、CLR寄宿与AppDomain(CLR Hosting and App Domains ) 二十九、原始线程同步构造(Primitive Thread Synchronization Constructs ) 二十六、线程与并发(Thread Basic) 二十七、计算密集型异步操作(Compute-Bound Asynchronous Operations) 二十三、程序集加载与反射(Assembly Loading and Reflection)
⏱️ 深入理解定时器中的【时间轮算法】
世纪末の魔术师 · 2025-12-25 · via 博客园 - 世纪末の魔术师
image

👉 时间轮(Timing Wheel) 是:

  • • 定时器
  • • 网络框架(Netty)
  • • 游戏服务器
  • • Unity 战斗/BUFF/技能/CD
  • • 事件调度系统

里的核心算法之一,属于**“看似基础,实则是对抽象极好的应用”**的内容。

⏱️ 深入理解定时器中的【时间轮算法】

—— 为什么它是 O(1) 的定时器神器?

一、为什么我们需要时间轮?

先从一个真实问题说起。

🎮 Unity 中的常见定时需求

  • • 技能 CD(5s、30s、120s)
  • • BUFF 持续时间
  • • DOT / HOT
  • • 延迟事件(3 秒后播放动画)
  • • 网络心跳 / 超时检测
  • • AI 行为延迟执行

最直观写法:

List<Timer> timers;

foreach (var t in timers)
{
    if (Time.time >= t.TriggerTime)
        t.Execute();
}

❌ 问题是什么?

  • • 每帧遍历所有定时器
  • • 时间复杂度:O(n)
  • • n 大了直接炸(服务器尤甚)

二、传统定时器方案对比(为什么不行)

List 数组 O(1) O(n) 每帧扫描 SortedList 有序数组 O(n) O(1) 插入慢 小顶堆 PriorityQueue O(log n) O(log n) 高频触发压力大 红黑树 Tree O(log n) O(log n) 实现复杂
方案 数据结构 插入 触发 问题

👉 有没有 O(1) 插入 + O(1) 触发?

答案是:时间轮

三、时间轮的核心思想(一句话版)

用“时间槽”代替“时间点”,
把未来的任务提前放进对应的槽里,
时间走到哪,就只处理那个槽。

四、时间轮的直觉模型(钟表模型)

image
  • • 一个环形数组
  • • 每个格子叫 Slot(槽)
  • • 每个槽存多个定时任务
  • • 指针每 Tick 前进一步

五、一个任务如何被调度?

假设:

  • • 时间轮刻度 = 1 秒
  • • 槽数量 = 8
  • • 当前指针在 Slot 2
  • • 新任务:5 秒后执行

计算位置:

slotIndex = (currentIndex + delay) % slotCount
          = (2 + 5) % 8
          = 7

👉 把任务放进 Slot 7

六、时间轮工作流程(图解)

image

七、时间复杂度为什么是 O(1)?

插入任务:

  • • 直接计算槽位
  • • 加入 List
  • • O(1)

Tick 执行:

  • • 只处理当前槽
  • • O(1)(均摊)

总结:

时间轮用空间换时间,把“排序问题”变成“索引问题”

八、单层时间轮的缺陷

⚠️ 问题来了:

  • • 最大延迟 = slotCount * tickDuration
  • • 如果要支持:
    • • 1 小时
    • • 24 小时
    • • 7 天

❌ 单层时间轮不够

九、多层时间轮(Hierarchical Timing Wheel)

类比现实钟表:

Level 0 秒针 60 Level 1 分针 60 Level 2 时针 24
层级 现实 槽数

多层结构图

image

Level 0 (Seconds) Level 1 (Minutes) Level 2 (Hours)
  • • 当前层走完一圈
  • • 推动上层
  • • 任务逐级下沉

十、简易版时间轮实现(核心示例)

定时任务结构

public class TimerTask
{
    public int RemainingRounds;
    public Action Callback;
}

时间轮核心结构

public class TimeWheel
{
    private readonly List<TimerTask>[] slots;
    private int currentIndex;

    public TimeWheel(int slotCount)
    {
        slots = new List<TimerTask>[slotCount];
        for (int i = 0; i < slotCount; i++)
            slots[i] = new List<TimerTask>();
    }

    public void AddTask(int delay, Action callback)
    {
        int index = (currentIndex + delay) % slots.Length;
        int rounds = delay / slots.Length;

        slots[index].Add(new TimerTask
        {
            RemainingRounds = rounds,
            Callback = callback
        });
    }

    public void Tick()
    {
        var list = slots[currentIndex];

        for (int i = list.Count - 1; i >= 0; i--)
        {
            var task = list[i];
            if (task.RemainingRounds <= 0)
            {
                task.Callback();
                list.RemoveAt(i);
            }
            else
            {
                task.RemainingRounds--;
            }
        }

        currentIndex = (currentIndex + 1) % slots.Length;
    }
}

使用示例

TimeWheel wheel = new TimeWheel(60);

void Start()
{
    wheel.AddTask(5, () => Debug.Log("5 秒后执行"));
}

void Update()
{
    if (Time.frameCount % 60 == 0)
        wheel.Tick();
}

十一、时间轮 vs Unity Coroutine

大量定时 ❌ GC 压力 ✅ 精度 帧级 Tick 级 可控性 差 强 服务器适配 ❌ ✅ 性能 中 极高
维度 Coroutine 时间轮

👉 服务器 / 大规模定时任务:时间轮完胜

十二、典型应用场景(你可以写进简历)

  • • MMO 技能冷却系统
  • • BUFF 生命周期管理
  • • 网络超时 / 心跳检测
  • • 延迟消息队列
  • • AI 行为调度
  • • ECS 世界 Tick 系统

十三、常见坑点总结

❌ Tick 精度过小 → 槽爆炸
❌ Tick 精度过大 → 任务延迟
❌ 忘记多层轮 → 最大延迟不够
❌ List 未反向遍历 → Remove 崩
❌ 回调中 AddTask → 注意线程安全

十四、终极总结一句话

时间轮不是“更快的排序”,
而是“直接取消排序需求”的时间结构设计。

🎯 面试题

1️⃣ 为什么时间轮插入是 O(1)?

因为通过时间取模直接定位槽位,不需要排序。

2️⃣ 时间轮和小顶堆定时器的核心差异?

堆依赖排序,时间轮依赖索引。

3️⃣ 单层时间轮最大延迟怎么算?

slotCount × tickDuration

4️⃣ 为什么服务器更偏好时间轮?

大量定时任务下,堆的 log n 成本不可控。

5️⃣ Unity 中哪些系统适合时间轮?

技能 CD、BUFF、延迟事件、网络超时。