Existe-t-il un moyen d'effectuer des tâches répétitives à intervalles réguliers?

149

Existe-t-il un moyen d'effectuer des tâches répétitives en arrière-plan dans Go? Je pense à quelque chose comme Timer.schedule(task, delay, period)à Java. Je sais que je peux faire ça avec un goroutine et Time.sleep(), mais j'aimerais quelque chose qui s'arrête facilement.

Voici ce que j'ai, mais me semble laid. Existe-t-il un moyen plus propre / meilleur?

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)
}
Steve Brisk
la source
3
Merci d'utiliser time.Duration (x) dans votre exemple. Chaque exemple que j'ai pu trouver a un int codé en dur et il se plaint lorsque vous utilisez un int (ou float) vars.
Mike Graf
@MikeGraf vous pouvez faire t := time.Tick(time.Duration(period) * time.Second)où la période est uneint
florianrosenberg
cette solution semble plutôt bonne, IMO. esp. si vous appelez simplement f () au lieu de l'heure externe. idéal pour les cas où vous voulez travailler x secondes après la fin du travail, par rapport à un intervalle constant.
Luke W

Réponses:

240

La fonction time.NewTickercrée un canal qui envoie un message périodique et fournit un moyen de l'arrêter. Utilisez-le quelque chose comme ça (non testé):

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

Vous pouvez arrêter le travailleur en fermant le quitcanal: close(quit).

Paul Hankin
la source
9
La réponse est incorrecte selon ce que veut l'OP. Si l'OP souhaite une exécution périodique quel que soit le temps utilisé par le travailleur, vous devrez exécuter do stuffune routine go, sinon le travailleur suivant s'exécuterait immédiatement (lorsqu'il aurait besoin de plus de 5 secondes).
nemo
2
IMO, vous devriez juste au close(quit)moment où vous souhaitez arrêter le planificateur.
Dustin
3
L'arrêt du ticker fonctionne, mais le goroutine ne sera jamais ramassé.
Paul Hankin
4
@SteveBrisk Voir la doc . Si le canal est fermé, une lecture réussira et vous ne voudrez peut-être pas cela.
nemo
10
@ bk0, les canaux horaires ne "sauvegardent" pas (la documentation dit "Il ajuste les intervalles ou supprime les graduations pour compenser la lenteur des récepteurs"). Le code donné fait exactement ce que vous dites (exécute au plus une tâche); si la tâche prend beaucoup de temps, la prochaine invocation sera simplement retardée; aucun mutex requis. Si à la place, vous souhaitez qu'une nouvelle tâche démarre à chaque intervalle (même si la précédente n'est pas terminée), utilisez simplement go func() { /*do stuff */ }().
Dave C
26

Et quelque chose comme

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")
}

Terrain de jeux

Volker
la source
3
A time.Tickerest préférable à l' time.Afterendroit où vous préférez maintenir la tâche dans les délais, par rapport à un écart arbitraire entre les exécutions.
Dustin
5
@Dustin Et c'est mieux si vous voulez effectuer un travail avec un écart fixe entre la fin et le début des tâches. Ni l'un ni l'autre n'est le meilleur - ce sont deux cas d'utilisation différents.
nos
`` // Après attend que la durée s'écoule, puis envoie l'heure actuelle // sur le canal retourné. // C'est équivalent à NewTimer (d) .C. // Le minuteur sous-jacent n'est pas récupéré par le garbage collector // jusqu'à ce que le minuteur se déclenche. Si l'efficacité est un problème, utilisez NewTimer `` Que diriez-vous de cette déclaration:If efficiency is a concern, use NewTimer
lee
23

Si vous ne vous souciez pas du décalage des ticks (en fonction du temps que cela a pris précédemment à chaque exécution) et que vous ne souhaitez pas utiliser de canaux, il est possible d'utiliser la fonction de plage native.

c'est à dire

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")
    }
}

Terrain de jeux

Alekc
la source
19

Consultez cette bibliothèque: https://github.com/robfig/cron

Exemple comme ci-dessous:

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()
Browny Lin
la source
3

Une réponse plus large à cette question pourrait considérer l'approche brique Lego souvent utilisée en Occam, et proposée à la communauté Java via JCSP . Il y a une très bonne présentation de Peter Welch sur cette idée.

Cette approche plug-and-play se traduit directement par Go, car Go utilise les mêmes principes de base du processus séquentiel de communication que Occam.

Ainsi, quand il s'agit de concevoir des tâches répétitives, vous pouvez construire votre système comme un réseau de flux de données de composants simples (comme des goroutines) qui échangent des événements (c'est-à-dire des messages ou des signaux) via des canaux.

Cette approche est compositionnelle: chaque groupe de petits composants peut lui-même se comporter comme un composant plus grand, à l'infini. Cela peut être très puissant car les systèmes concurrents complexes sont fabriqués à partir de briques faciles à comprendre.

Note de bas de page: dans la présentation de Welch, il utilise la syntaxe Occam pour les canaux, qui est ! et ? et ceux-ci correspondent directement à ch <- et <-ch dans Go.

Rick-777
la source
3

J'utilise le code suivant:

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")
}

C'est plus simple et ça me convient.

Gustavo Emmel
la source
0

Si vous voulez l'arrêter à tout moment, ticker

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

Si vous ne souhaitez pas l'arrêter, cochez :

tick := time.Tick(500 * time.Millisecond)
for range tick {
    fmt.Println("Tick")
}
John Balvin Arias
la source