有没有办法定期执行重复性任务?


148

有没有办法在Go中执行重复的后台任务?我在想类似Timer.schedule(task, delay, period)Java 的东西。我知道我可以使用goroutine和来做到这一点Time.sleep(),但是我想要一些容易停止的东西。

这就是我得到的,但是对我来说看起来很丑。有没有更清洁/更好的方法?

func oneWay() {
    var f func()
    var t *time.Timer

    f = func () {
        fmt.Println("doing stuff")
        t = time.AfterFunc(time.Duration(5) * time.Second, f)
    }

    t = time.AfterFunc(time.Duration(5) * time.Second, f)

    defer t.Stop()

    //simulate doing stuff
    time.Sleep(time.Minute)
}

3
感谢您在示例中使用time.Duration(x)。我可以找到的每个示例都有一个硬编码的int,并且在使用int(或float)变量时会抱怨。
Mike Graf

@MikeGraf您可以t := time.Tick(time.Duration(period) * time.Second)在时间段不长的地方做int
florianrosenberg

IMO,这个解决方案看起来还不错。尤其是 如果您只是简单地调用f()而不是外部时间。非常适合您想要在工作完成后x秒钟进行工作的情况,而不是一个固定的时间间隔。
卢克W

Answers:


240

该函数time.NewTicker使一个通道发送定期消息,并提供了一种停止该消息的方法。使用它像这样(未经测试):

ticker := time.NewTicker(5 * time.Second)
quit := make(chan struct{})
go func() {
    for {
       select {
        case <- ticker.C:
            // do stuff
        case <- quit:
            ticker.Stop()
            return
        }
    }
 }()

您可以通过关闭quit渠道来停止工作人员:close(quit)


9
答案是不正确的,取决于OP的要求。如果OP希望无论该工作人员使用了多少时间都希望定期执行,则您必须do stuff在go例程中运行,否则下一个工作人员将立即执行(需要5秒以上)。
nemo

2
IMO,您应该仅close(quit)在要停止调度程序时使用。
达斯汀

3
停止行情收录器是可行的,但是goroutine将永远不会被垃圾回收。
保罗·汉金

4
@SteveBrisk参见文档。如果通道关闭,则读取将成功,您可能不希望这样做。
nemo,

10
@ bk0,时间通道不“备份”(文档说“它调整间隔或滴答滴答声以弥补慢速接收者”)。给定的代码完全符合您的要求(最多执行一项任务);如果任务花费很长时间,则下一次调用将被延迟;不需要互斥。相反,如果希望每个时间间隔开始一个新任务(即使前一个任务还没有完成),请使用go func() { /*do stuff */ }()
戴夫C

26

怎么样

package main

import (
    "fmt"
    "time"
)

func schedule(what func(), delay time.Duration) chan bool {
    stop := make(chan bool)

    go func() {
        for {
            what()
            select {
            case <-time.After(delay):
            case <-stop:
                return
            }
        }
    }()

    return stop
}

func main() {
    ping := func() { fmt.Println("#") }

    stop := schedule(ping, 5*time.Millisecond)
    time.Sleep(25 * time.Millisecond)
    stop <- true
    time.Sleep(25 * time.Millisecond)

    fmt.Println("Done")
}

操场


3
A time.Ticker优于time.After您希望按计划执行任务的位置,而不是执行之间的任意间隔。
达斯汀

5
@Dustin如果您想在任务的结束和开始之间保持固定的间隔执行工作,则更好。两者都不是最好的-这是两个不同的用例。
2015年

```//等待持续时间过去,然后在返回的通道上发送当前时间//。//等效于NewTimer(d).C。//直到计时器触发后,底层的Timer才由垃圾收集器恢复。如果效率是一个问题,请使用NewTimer```此语句如何:If efficiency is a concern, use NewTimer
lee

23

如果您不关心刻度移动(取决于每次执行之前花费了多长时间)并且不想使用通道,则可以使用本机范围函数。

package main

import "fmt"
import "time"

func main() {
    go heartBeat()
    time.Sleep(time.Second * 5)
}

func heartBeat() {
    for range time.Tick(time.Second * 1) {
        fmt.Println("Foo")
    }
}

操场


19

看看这个库:https : //github.com/robfig/cron

示例如下:

c := cron.New()
c.AddFunc("0 30 * * * *", func() { fmt.Println("Every hour on the half hour") })
c.AddFunc("@hourly",      func() { fmt.Println("Every hour") })
c.AddFunc("@every 1h30m", func() { fmt.Println("Every hour thirty") })
c.Start()

3

对于这个问题的更广泛的答案可能是考虑在Occam中经常使用的Lego积木方法,该方法是通过JCSP提供给Java社区的。Peter Welch很好地介绍了这个想法。

这种即插即用的方法直接转换为Go,因为Go使用与Occam相同的“通信顺序过程”基础知识。

因此,在设计重复性任务时,您可以将系统构建为简单组件(如goroutine)的数据流网络,这些组件可以通过通道交换事件(即消息或信号)。

这种方法是组合的:每组小的组件本身都可以无限地充当较大的组件。这可能非常强大,因为复杂的并发系统是由易于理解的积木组成的。

脚注:在Welch的演示中,他对频道使用Occam语法,即它们直接对应于Go中的ch <-<-ch


3

我使用以下代码:

package main

import (
    "fmt"
    "time"
)

func main() {
    now := time.Now()
    fmt.Println("\nToday:", now)

    after := now.Add(1 * time.Minute)
    fmt.Println("\nAdd 1 Minute:", after)

    for {
        fmt.Println("test")
        time.Sleep(10 * time.Second)

        now = time.Now()

        if now.After(after) {
            break
        }
    }

    fmt.Println("done")
}

它更简单,对我来说很好用。


0

如果您想随时停止它,则置顶

ticker := time.NewTicker(500 * time.Millisecond)
go func() {
    for range ticker.C {
        fmt.Println("Tick")
    }
}()
time.Sleep(1600 * time.Millisecond)
ticker.Stop()

如果您不想停止它,请打勾

tick := time.Tick(500 * time.Millisecond)
for range tick {
    fmt.Println("Tick")
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.