时间轮算法

据说是复杂度O(1)的牛逼算法,所以抽时间学习学习。

现在要实现一个定时器,这个定时器控制很多任务。该怎么做呢?

第一反应是任务做成一个队列,属性有个时间,每次计时后将该属性减1,到0的时候就执行。这种方式可行,但是效率不高,因为每次都要遍历所有任务,所以时间复杂度是O(N)。

优化的方法是什么呢?有点类似哈希表,增加一个时间队列,同时将任务预先排放在一个时间队列中。如果是100秒的时间范围,那么就是100个队列,10秒延时的任务就放在第9个(起始是0),20秒延时的任务就放在第19个,这里的位置就叫做槽slot。这样每次计数的时候就不用去遍历任务队列,只用去看当前时间队列有没有任务就行了。

如果时间很长呢?这个队列占用的内存岂不是无止境,所以改成循环的方式,比如60秒是一个循环,那么100秒就是100/60=1,此时余数是40,所以这个任务就放在slot40,此时圈数circle就是1。之后在时间间隔中挨着减就可以了。虽然增加了一个时间队列,但是这个耗费不算多,但是算法的时间复杂度就变成了O(1)。

算法示意如下:

python代码:

import threading
import time
from collections import defaultdict

class Task:
    def __init__(self, delay, callback, args=()):
        self.delay = delay  # 单位秒
        self.callback = callback
        self.args = args
        self.circle = 0  # 圈数
        self.slot = 0    # 槽位

class TimingWheel:
    def __init__(self, interval=1, slot_num=60):
        self.interval = interval      # 每个tick的时间间隔(秒)
        self.slot_num = slot_num      # 槽数量(环大小)
        self.slots = defaultdict(list)
        self.current_slot = 0
        self.lock = threading.Lock()
        self.running = False
        self.thread = None

    def add_task(self, task):
        with self.lock:
            ticks = int(task.delay / self.interval)
            task.slot = (self.current_slot + ticks) % self.slot_num
            task.circle = ticks // self.slot_num
            self.slots[task.slot].append(task)

    def tick(self):
        with self.lock:
            slot_tasks = self.slots[self.current_slot]
            new_tasks = []
            for task in slot_tasks:
                if task.circle > 0:
                    task.circle -= 1
                    new_tasks.append(task)
                else:
                    threading.Thread(target=task.callback, args=task.args).start()
            self.slots[self.current_slot] = new_tasks
            self.current_slot = (self.current_slot + 1) % self.slot_num

    def start(self):
        self.running = True
        def run():
            while self.running:
                time.sleep(self.interval)
                self.tick()
        self.thread = threading.Thread(target=run)
        self.thread.start()

    def stop(self):
        self.running = False
        if self.thread:
            self.thread.join()

# 示例用法
def my_task(name):
    print(f"[{time.strftime('%X')}] Task executed: {name}")

if __name__ == "__main__":
    wheel = TimingWheel(interval=1, slot_num=10)
    wheel.start()

    # 添加几个任务
    wheel.add_task(Task(3, my_task, args=("Task A",)))
    wheel.add_task(Task(7, my_task, args=("Task B",)))
    wheel.add_task(Task(12, my_task, args=("Task C",)))
    wheel.add_task(Task(25, my_task, args=("Task D",)))

    time.sleep(30)  # 等待任务完成
    wheel.stop()

最后一个小问题,槽数怎么定?

一般公式是:

槽数 = 最大期望延迟 / 时间粒度

如果想支持最长 10 秒的延迟,精度是 100ms,则: 

槽数 = 10s / 0.1s = 100

一些典型的槽数:

使用场景推荐粒度推荐槽数范围
网络心跳检测1s60~600
游戏逻辑更新100ms100~1000
高频延时任务1ms1000+
大跨度任务(分钟级)1s 或 1min分层时间轮更合适

好了,先到这里吧。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值