RussellLuo / timingwheel

Golang implementation of Hierarchical Timing Wheels.
MIT License
660 stars 125 forks source link

Scheduler 例子的问题 #30

Closed Alecyrus closed 3 years ago

Alecyrus commented 4 years ago
// We need to stop the timer since it will be restarted again and again.
    for !t.Stop() {
    }

我不明白为什么要用 for 循环去处理 t.Stop(), 按照 Stop() 的文档:

Stop prevents the Timer from firing. It returns true if the call stops the timer, false if the timer has already expired or been stopped.

If the timer t has already expired and the t.task has been started in its own goroutine; Stop does not wait for t.task to complete before returning. If the caller needs to know whether t.task is completed, it must coordinate with t.task explicitly.

如果返回true,则 timer 被停止了,如果返回 false,则 timer 已经过期了或已经被停止了。我的理解是,无论返回true还是false,都说明这个周期任务已经不会被触发了,这是我单单只看这个Stop()文档的理解。

但是我仔细的读了下 Stop()的源码:

func (t *Timer) Stop() bool {
    stopped := false
    for b := t.getBucket(); b != nil; b = t.getBucket() {
        // If b.Remove is called just after the timing wheel's goroutine has:
        //     1. removed t from b (through b.Flush -> b.remove)
        //     2. moved t from b to another bucket ab (through b.Flush -> b.remove and ab.Add)
        // this may fail to remove t due to the change of t's bucket.
        stopped = b.Remove(t)

        // Thus, here we re-get t's possibly new bucket (nil for case 1, or ab (non-nil) for case 2),
        // and retry until the bucket becomes nil, which indicates that t has finally been removed.
    }
    return stopped
}

以上代码中,我的理解是,只要 timer 还可以取到 bucket, 那就要从 bucket 去 remove 这个 timer,直到 timer 无法取到 bucket (这就说明 这个timer已经从时间轮中被移出,对应的函数也不会再被触发了,也就是 停止成功, 返回 false),所以的确和上面的描述是一致的。。

然后我就看不懂这里为什么要加个 for 循环

for !t.Stop() {
    }

然后注释说 since it will be restarted again and again., 如果 t.Stop() 返回 false 的话,那这里不就无限循环了,,为什么不直接 t.Stop() 嘞,反正按照 Stop() 的逻辑,只要 Stop() 执行完了,t 就不会在fire了。

所以我不太明白这里代码 for 循环的意义何在?希望大佬可以指导一下 @RussellLuo

RussellLuo commented 4 years ago

@Auxxxxlx 谢谢关注!

你提到的 Stop 方式和相关注释,出现在 ScheduleTimer 示例中,是专门针对定时任务的。

定时任务目前是 自调度 的,大致流程为:

  1. 创建一个初始 timer(bucket.Add)
  2. timer 到期,执行 task(bucket.Remove)
  3. 在 task 中,更新 timer 的 expiration,并重新将 timer 加入时间轮(bucket.Add)
  4. 在 task 中执行 f
  5. (回到第 2 步,如此反复...)

如果只调用一次 Stop() ,并且刚好在第 2 步和第 3 步之间,此时 timer 不在 bucket 中,于是 Stop() 会返回 false;此后第 3 步又会把 timer 重新加入时间轮,因此 timer 并不没有被 Stop。

Stop 定时任务的正确方式,在 ScheduleFunc 的文档中有简要说明