.Net 之时间轮算法(终极版)
关于时间轮算法的起始
我也认真的看了时间轮算法相关,大致都是如下的一个图




个人认为的问题
大部分文章在解释这个为何用时间轮的时候都再说
假设我们现在有一个很大的数组,专门用于存放延时任务。它的精度达到了毫秒级!那么我们的延迟任务实际上需要将定时的那个时间简单转换为毫秒即可,然后将定时任务存入其中:
比如说当前的时间是 2018/10/24 19:43:45,那么就将任务存入 Task[1540381425000],value 则是定时任务的内容。
假如这个数组长度达到了亿亿级,我们确实可以这么干。 那如果将精度缩减到秒级呢?我们也需要一个百亿级长度的数组。
先不说内存够不够,显然你的定时器要这么大的内存显然很浪费。
为什么要用时间轮实现
- 通常用于实现 linux 内核任务、游戏类的 buf 计时。
- 单个时间轮局限性:保存的任务数量少,不能超过当前时间轮。
- 多层时间轮,典型:日-时-分-秒
- 传统 java 实现定时:Timer,只能单线程,会阻塞; Executors.newScheduledThreadPoll, 使用的最小堆来实现,任务还是不能太多,添加时间复杂度为 O(logn)
时间轮定时器最大的优点
- 是任务的添加与移除,都是 O(1)级的复杂度;
- 不会占用大量的资源;
- 只需要有一个线程去推进时间轮就可以工作了
举例说明
比如说当前的时间是 2018/10/24 19:43:45,那么就将任务存入 Task[1540381425000],value 则是定时任务的内容。
private Task[很长] tasks;
public List<Task> getTaskList(long timestamp) {
return task.get(timestamp)
}
// 假装这里真的能一毫秒一个循环
public void run(){
while (true){
getTaskList(System.currentTimeMillis()).后台执行()
Thread.sleep(1);
}
}
假如这个数组长度达到了亿亿级,我们确实可以这么干。 那如果将精度缩减到秒级呢?我们也需要一个百亿级长度的数组。
先不说内存够不够,显然你的定时器要这么大的内存显然很浪费。
基于个人的理解对其进行改造和实现
对于以上的描述,我自己还是很不认同的,我为啥要声明这么大的数组,难道不能有一个任务,我就放一个任务么,实际数组的长度应该是你任务数的长度吧。
要不然,为啥要这么浪费。想不通,可能还有别的解释,谁有答案可以告诉我。
在实际的使用中,一般都为秒级,毫秒级都很少,因为毫秒级的不准。
所以,我可以根据这些通过 hash 字典构建一个 这样的时间轮算法,也作为我自己想实现定时任务框架的一部分。
逻辑:
核心为一个字典,key 为时间戳,值为任务列表。
整体就是每个添加的任务都有一个触发的时间(秒),到了这个秒,时间就会触发,自然会去执行相关的任务。
有了任务才会添加,不会任何任务都添加的。
任务触发的时候,会获取任务下一次执行的时间,并插入到任务列表里。
static void Main(string[] args)
{
ScheduledTask scheduledTask = new ScheduledTask(() => { Console.WriteLine($"{DateTime.Now}"); }, new TimeSpan(0, 0, 5));
TimeWheel timeWheel = new TimeWheel();
timeWheel.AddTask(scheduledTask);
timeWheel.Run();
Console.WriteLine("开始运行时间轮!");
Console.ReadLine();
}
/// <summary>
/// 时间轮算法(终极)实现
/// 大部分都是支持秒级,所以,按照秒级进行实现
/// </summary>
public class TimeWheel
{
/// <summary>
/// 任务列表
/// </summary>
public ConcurrentDictionary<long, List<IScheduledTask>> Tasks = new();
public void Run()
{
while (true)
{
var now = DateTime.Now;
Task.Run(() => { Trigger(now); });
var offset = 500 - now.Millisecond;
SpinWait.SpinUntil(() => false, 1000 + offset);
}
}
public void Trigger(DateTime dateTime)
{
var timeStamp = GenerateTimestamp(dateTime);
var oldTimeStamp = timeStamp - 1;
Tasks.TryRemove(oldTimeStamp, out var _);
Tasks.TryGetValue(timeStamp, out var result);
if (result?.Any() == true)
{
foreach (var item in result)
{
Task.Run(item.GetAction());
var NewTime = item.GetNextTime();
if (NewTime.HasValue)
{
AddTask(NewTime.Value, item);
}
}
}
}
public void AddTask(IScheduledTask scheduledTask)
{
var timeStamp = GenerateTimestamp(scheduledTask.GetNextTime().Value);
Tasks.AddOrUpdate(timeStamp, new List<IScheduledTask>() { scheduledTask }, (k, v) =>
{
v.Add(scheduledTask);
return v;
});
}
public void AddTask(DateTime dateTime, IScheduledTask scheduledTask)
{
var timeStamp = GenerateTimestamp(dateTime);
Tasks.AddOrUpdate(timeStamp, new List<IScheduledTask>() { scheduledTask }, (k, v) =>
{
v.Add(scheduledTask);
return v;
});
}
private long GenerateTimestamp(DateTime dateTime)
{
return new DateTimeOffset(dateTime.ToUniversalTime()).ToUnixTimeSeconds();
}
private DateTime GetDatetime(long timeStamp)
{
var d = DateTimeOffset.FromUnixTimeSeconds(timeStamp);
return d.LocalDateTime;
}
}
public interface IScheduledTask
{
public Action GetAction();
public DateTime? GetNextTime();
}
/// <summary>
/// 定时器任务,普通任务
/// 间隔指定的时间
/// </summary>
public class ScheduledTask : IScheduledTask
{
private Action _action;
private TimeSpan timeSpan;
public ScheduledTask(Action action, TimeSpan timeSpan)
{
this._action = action;
this.timeSpan = timeSpan;
}
public Action GetAction()
{
return this._action;
}
public DateTime? GetNextTime()
{
return DateTime.Now.AddSeconds(timeSpan.TotalSeconds);
}
}
最后的效果

当然,也可以通过 CRON 表达式来实现更为高级的。
后期再来个更高级一点的。
github 地址
https://github.com/kesshei/TimeWheelDemo
.Net 之时间轮算法(终极版)的更多相关文章
- .Net之时间轮算法(终极版)定时任务
TimeWheelDemo 一个基于时间轮原理的定时器 对时间轮的理解 其实我是有一篇文章(.Net 之时间轮算法(终极版))针对时间轮的理论理解的,但是,我想,为啥我看完时间轮原理后,会采用这样的方 ...
- 时间轮算法的定时器(Delphi)
源码下载 http://files.cnblogs.com/lwm8246/uTimeWheel.rar D7,XE2 编译测试OK //时间轮算法的定时器 //-- : QQ unit uTimeW ...
- 时间轮算法(TimingWheel)是如何实现的?
前言 我在2. SOFAJRaft源码分析-JRaft的定时任务调度器是怎么做的?这篇文章里已经讲解过时间轮算法在JRaft中是怎么应用的,但是我感觉我并没有讲解清楚这个东西,导致看了这篇文章依然和没 ...
- 时间轮算法在Netty和Kafka中的应用,为什么不用Timer、延时线程池?
大家好,我是yes. 最近看 Kafka 看到了时间轮算法,记得以前看 Netty 也看到过这玩意,没太过关注.今天就来看看时间轮到底是什么东西. 为什么要用时间轮算法来实现延迟操作? 延时操作 Ja ...
- 延时任务-基于netty时间轮算法实现
一.时间轮算法简介 为了大家能够理解下文中的代码,我们先来简单了解一下netty时间轮算法的核心原理 时间轮算法名副其实,时间轮就是一个环形的数据结构,类似于表盘,将时间轮分成多个bucket(比如: ...
- [从源码学设计]蚂蚁金服SOFARegistry之时间轮的使用
[从源码学设计]蚂蚁金服SOFARegistry之时间轮的使用 目录 [从源码学设计]蚂蚁金服SOFARegistry之时间轮的使用 0x00 摘要 0x01 业务领域 1.1 应用场景 0x02 定 ...
- kafka时间轮的原理(一)
概述 早就想写关于kafka时间轮的随笔了,奈何时间不够,技术感觉理解不到位,现在把我之前学习到的进行整理一下,以便于以后并不会忘却.kafka时间轮是一个时间延时调度的工具,学习它可以掌握更加灵活先 ...
- 经典多级时间轮定时器(C语言版)
经典多级时间轮定时器(C语言版) 文章目录 经典多级时间轮定时器(C语言版) 1. 序言 2. 多级时间轮实现框架 2.1 多级时间轮对象 2.2 时间轮对象 2.3 定时任务对象 2.4 双向链表 ...
- 08重编终极版《东邪西毒:终极版》DVD粤语中字
1.东邪西毒].Ashes.of.Time.1994.384p.DVDRip.x264.ac3-DTMM.mkv 这个版本最清晰 ,可惜删减了,只有87分钟,粤语,1.4G. 2.东邪西毒(初始版). ...
随机推荐
- 从 Notion 分片 Postgres 中吸取的教训(Notion 工程团队)
https://www.notion.so/blog/sharding-postgres-at-notion 今年(2021)早些时候,我们对 Notion 进行了五分钟的定期维护. 虽然我们的声明指 ...
- 【多线程】线程礼让 Thread.yield()
线程礼让 Thread.yield() 礼让线程,让当前正在执行的线程暂停,但不阻塞 : 将线程从运行状态转为就绪状态 : 让cpu重新调度,礼让不一定成功!看CPU心情. 代码示例: /** * @ ...
- awk内建函数
内建函数 length() 获得字符串长度 cat score.txt Marry 2143 78 84 77 Jack 2321 66 78 45 Tom 2122 48 77 71 Mike 25 ...
- 零基础学Java第六节(面向对象二)
本篇文章是<零基础学Java>专栏的第六篇文章,文章采用通俗易懂的文字.图示及代码实战,从零基础开始带大家走上高薪之路! 本文章首发于公众号[编程攻略] 继承 创建一个Person类 我们 ...
- 设计并实现大数类 BigNum
学习任务:设计并实现大数类 BigNum 代码示例: import java.util.Scanner; public class BigNum { private double num; publi ...
- 从URL输入到页面展现到底发生什么?DNS 解析&TCP 连接
DNS 解析:将域名解析成 IP 地址 TCP 连接:TCP 三次握手 发送 HTTP 请求 服务器处理请求并返回 HTTP 报文 浏览器解析渲染页面 断开连接:TCP 四次挥手 一.什么是URL? ...
- ethtools-网卡适配器管理
查看网卡适配器配置信息,并且我们可以通过它修改网卡适配器的双工模式. 1.安装Ethtools [root@localhost ~]# yum -y install ethtools 2.命令语法 语 ...
- Hadoop: 单词计数(Word Count)的MapReduce实现
1.Map与Reduce过程 1.1 Map过程 首先,Hadoop会把输入数据划分成等长的输入分片(input split) 或分片发送到MapReduce.Hadoop为每个分片创建一个map任务 ...
- Stream.toList()和Collectors.toList()的性能比较
昨天给大家介绍了Java 16中的Stream增强,可以直接通过toList()来转换成List. 主要涉及下面这几种转换方式: list.stream().toList(); list.stream ...
- 写个js获取2019博客之星投票活动的名次与投票数
获取投票数 // app.jsvar request = require('request');var cheerio = require('cheerio');request('http://m23 ...