C'è un modo per svolgere attività ripetitive a intervalli?


149

Esiste un modo per eseguire attività in background ripetitive in Go? Sto pensando a qualcosa di simile Timer.schedule(task, delay, period)a Java. So di poterlo fare con una goroutine e Time.sleep(), ma vorrei qualcosa che si fermasse facilmente.

Ecco cosa ho ottenuto, ma mi sembra brutto. C'è un modo più pulito / migliore?

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
Grazie per aver utilizzato time.Duration (x) nel tuo esempio. Ogni esempio che ho trovato ha un int codificato e si lamenta quando usi un int (o float) var.
Mike Graf,

@MikeGraf puoi fare t := time.Tick(time.Duration(period) * time.Second)dove il periodo è unint
florianrosenberg il

questa soluzione sembra abbastanza buona, IMO. esp. se chiami semplicemente f () invece del tempo esterno. AfterFunc. ideale per i casi in cui si desidera lavorare x secondi dopo il completamento del lavoro, rispetto a un intervallo coerente.
Luca W,

Risposte:


240

La funzione time.NewTickercrea un canale che invia un messaggio periodico e fornisce un modo per fermarlo. Usalo in questo modo (non testato):

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

È possibile interrompere il lavoratore chiudendo il quitcanale di: close(quit).


9
La risposta non è corretta a seconda di ciò che l'OP desidera. Se l'OP desidera un'esecuzione periodica indipendentemente da quanto tempo impiega il lavoratore, è necessario eseguire do stuffuna routine go altrimenti il ​​lavoratore successivo verrà eseguito immediatamente (quando sono necessari più di 5 secondi).
nemo,

2
IMO, dovresti proprio close(quit)quando vuoi fermare lo scheduler.
Dustin,

3
L'arresto del ticker funziona, ma la goroutine non verrà mai raccolta rifiuti.
Paul Hankin,

4
@SteveBrisk Vedi il documento . Se il canale è chiuso, una lettura avrebbe semplicemente successo e potresti non volerlo.
nemo,

10
@ bk0, i canali temporali non "eseguono il backup" (la documentazione dice "Regola gli intervalli o elimina i tick per compensare i ricevitori lenti"). Il codice dato fa esattamente quello che dici (esegue al massimo un'attività); se l'attività richiede molto tempo, la prossima chiamata sarà semplicemente ritardata; nessun mutex richiesto. Se invece si desidera che una nuova attività venga avviata ad ogni intervallo (anche se la precedente non è terminata), utilizzare semplicemente go func() { /*do stuff */ }().
Dave C,

26

Che ne dici di qualcosa del genere

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

Terreno di gioco


3
A time.Tickerè meglio di time.Afterdove preferiresti mantenere l'attività nei tempi previsti rispetto a un divario arbitrario tra esecuzioni.
Dustin,

5
@Dustin E questo è meglio se vuoi eseguire un lavoro con uno spazio fisso tra la fine e l'inizio delle attività. Nessuno dei due è il migliore: sono due diversi casi d'uso.
nn.

`` // // After attende che trascorra la durata, quindi invia l'ora corrente // sul canale restituito. // È equivalente a NewTimer (d) .C. // Il timer sottostante non viene recuperato dal Garbage Collector // finché il timer non viene attivato. Se l'efficienza è un problema, usa NewTimer `` Che ne dici di questa affermazione:If efficiency is a concern, use NewTimer
lee

23

Se non ti interessa lo spostamento del segno di spunta (a seconda del tempo impiegato in precedenza per ciascuna esecuzione) e non desideri utilizzare i canali, è possibile utilizzare la funzione di intervallo nativo.

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

Terreno di gioco


19

Dai un'occhiata a questa libreria: https://github.com/robfig/cron

Esempio come di seguito:

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

Una risposta più ampia a questa domanda potrebbe prendere in considerazione l'approccio del mattoncino Lego spesso usato in Occam e offerto alla comunità Java tramite JCSP . C'è un'ottima presentazione di Peter Welch su questa idea.

Questo approccio plug-and-play si traduce direttamente in Go, perché Go utilizza gli stessi fondamenti del processo sequenziale comunicativo di Occam.

Quindi, quando si tratta di progettare attività ripetitive, è possibile costruire il proprio sistema come una rete di flusso di dati di componenti semplici (come goroutine) che scambiano eventi (ad esempio messaggi o segnali) tramite canali.

Questo approccio è compositivo: ogni gruppo di piccoli componenti può comportarsi da solo come un componente più grande, all'infinito. Questo può essere molto potente perché complessi sistemi concorrenti sono realizzati con mattoni di facile comprensione.

Nota in calce: nella presentazione di Welch, usa la sintassi Occam per i canali, che è ! e ? e questi corrispondono direttamente a ch <- e <-ch in Go.


3

Io uso il seguente codice:

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

È più semplice e funziona bene per me.


0

Se vuoi fermarlo in qualsiasi momento ticker

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

Se non vuoi fermarlo spunta :

tick := time.Tick(500 * time.Millisecond)
for range tick {
    fmt.Println("Tick")
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.