Golang time.Ticker 在时钟时间上打勾

Golang time.Ticker to tick on clock times

我正在开发一个 Go 程序,它要求我 运行 在(相当)准确的时钟时间执行某些功能(例如,每 5 分钟,但随后具体在 3:00,3:05, 3:10, 等等, 不仅仅是程序开始后每 5 分钟).

在来这里请求你的帮助之前,我尝试实现一个自动收报机来做到这一点,尽管它似乎工作正常,但感觉有点 dirty/hacky 并且它不是非常精确(它只是分数毫秒,但我想知道是否有理由相信差异会随着时间的推移而增加)。

我目前的实现如下,我真正想问的是,是否有更好的解决方案来实现我正在努力实现的目标(并且我可以对此更有信心)?

type ScheduledTicker struct {
    C chan time.Time
}

// NewScheduledTicker returns a ticker that ticks on defined intervals after the hour
// For example, a ticker with an interval of 5 minutes and an offset of 0 will tick at 0:00:00, 0:05:00 ... 23:55:00
// Using the same interval, but an offset of 2 minutes will tick at 0:02:00, 0:07:00 ... 23:57
func NewScheduledTicker(interval time.Duration, offset time.Duration) *ScheduledTicker {
    s := &ScheduledTicker{
        C: make(chan time.Time),
    }

    go func() {
        now := time.Now()

        // Figure out when the first tick should happen
        firstTick := now.Truncate(interval).Add(interval).Add(offset)

        // Block until the first tick
        <-time.After(firstTick.Sub(now))

        t := time.NewTicker(interval)

        // Send initial tick
        s.C <- firstTick

        for {
            // Forward ticks from the native time.Ticker to the ScheduledTicker channel
            s.C <- <-t.C
        }
    }()

    return s
}

所有平台上的大多数计时器 api 都根据系统时间而不是挂钟时间工作。你要表达的是有一个挂钟间隔。

正如其他答案所表达的那样,有可用的开源包。快速 Google 搜索“Golang Wall Clock ticker”会产生有趣的结果。

另一件事要考虑。在 Windows 上有“计划任务”,在 Linux 上有“cronjobs”,可以为您执行挂钟唤醒间隔。如果您的所有程序要做的是 sleep/tick 在执行所需工作之前的所需间隔之间,请考虑使用它。

但是如果你自己构建...

由于台式电脑在笔记本电脑盖合上时进入睡眠状态(暂停系统时间)以及系统时钟和挂钟之间的时钟偏差,因此尝试在挂钟间隔内完成工作变得复杂。有时用户喜欢更改他们 PC 的时钟 - 您可以醒来并轮询 time.Now 并发现您在昨天!这不太可能发生在云端的服务器 运行 上,但在个人设备上却是真实存在的。

在我的产品团队中,当我们确实需要时钟时间或需要在超过一个小时的时间间隔内做某事时,我们会以更频繁的时间间隔醒来,看看“时间到了”。例如,如果我们希望每 12 小时执行一次操作,我们可能会每小时醒来并轮询一次时间。 (我们在我工作的地方使用 C++ 而不是 Go)。

否则,我的 5 分钟间隔的一般算法是休眠(或设置系统计时器)1 分钟或更短时间。在从 time.Sleep 开始的每 return 之后,检查当前时间 (time.Now()) 以查看当前时间是否在下一个预期间隔时间或之后。 Post 您的频道事件,然后重新计算下一次唤醒时间。如果你假装早起,你甚至可以改变睡眠时间的粒度。

但是要小心! golang 时间对象包含挂钟和系统时钟时间。这包括由 Time.Now() 编辑的结果 return。 Time.Add()Time.Sub() 和其他一些时间比较函数首先在整体时间上工作,然后再转到挂钟时间。您可以通过执行以下操作从 Time.Now 结果中去除单片时间:

func GetWallclockNow() time.Time {
    var t time.Time = time.Now()
    return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location())
}

然后AddAfter等后续操作将在挂钟space。