Czy istnieje sposób na wykonywanie powtarzalnych zadań w odstępach czasu?

148

Czy istnieje sposób na wykonywanie powtarzalnych zadań w tle w Go? Myślę o czymś takim, jak Timer.schedule(task, delay, period)w Javie. Wiem, że mogę to zrobić za pomocą gorutyny i Time.sleep(), ale chciałbym coś, co można łatwo zatrzymać.

Oto, co mam, ale dla mnie wygląda brzydko. Czy istnieje czystszy / lepszy sposób?

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
źródło
3
Dziękujemy za użycie time.Duration (x) w Twoim przykładzie. Każdy przykład, który mogłem znaleźć, ma zakodowany na stałe int i narzeka, gdy używasz zmiennej int (lub float).
Mike Graf
@MikeGraf możesz zrobić t := time.Tick(time.Duration(period) * time.Second)tam , gdzie okres jestint
florianrosenberg
to rozwiązanie wydaje się całkiem dobre, IMO. zwł. jeśli po prostu wywołaj f () zamiast czasu zewnętrznego. świetne w przypadkach, w których chcesz pracować x sekund po wykonaniu pracy, w porównaniu do stałych interwałów.
Luke W

Odpowiedzi:

240

Funkcja time.NewTickertworzy kanał, który wysyła okresową wiadomość i zapewnia sposób jej zatrzymania. Użyj czegoś takiego (nieprzetestowane):

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

Można zatrzymać pracownika przez zamknięcie quitkanału: close(quit).

Paul Hankin
źródło
9
Odpowiedź jest nieprawidłowa w zależności od tego, czego chce PO. Jeśli OP chce wykonywać okresowe wykonywanie, niezależnie od tego, ile czasu zużywa pracownik, musiałbyś uruchomić do stuffrutynę go, w przeciwnym razie następny pracownik wykonałby natychmiast (gdy potrzebuje więcej niż 5 sekund).
nemo
2
IMO, powinieneś tylko close(quit)wtedy, gdy chcesz zatrzymać harmonogram.
Dustin
3
Zatrzymanie tickera działa, ale goroutine nigdy nie będzie zbierany jako śmieci.
Paul Hankin
4
@SteveBrisk Zobacz dokumentację . Jeśli kanał zostanie zamknięty, odczyt po prostu się powiedzie, a możesz tego nie chcieć.
nemo
10
@ bk0, kanały czasowe nie „archiwizują” (w dokumentacji jest napisane: „Dostosowuje interwały lub zmniejsza tiki, aby zrekompensować powolne odbiorniki”). Podany kod robi dokładnie to, co powiesz (uruchamia co najwyżej jedno zadanie); jeśli zadanie zajmuje dużo czasu, następne wywołanie będzie po prostu opóźnione; nie jest wymagany mutex. Jeśli zamiast tego pożądane jest, aby nowe zadanie było uruchamiane co interwał (nawet jeśli poprzednie nie zostało zakończone), po prostu użyj go func() { /*do stuff */ }().
Dave C
26

Co powiesz na coś takiego

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

Plac zabaw

Volker
źródło
3
A time.Tickerjest lepsze niż miejsce, w time.Afterktórym wolisz trzymać zadanie zgodnie z harmonogramem, a nie dowolna przerwa między wykonaniami.
Dustin
5
@Dustin I to jest lepsze, jeśli chcesz wykonywać pracę ze stałą przerwą między końcem a początkiem zadań. Żaden nie jest najlepszy - to dwa różne przypadki użycia.
nr
`` // After czeka na upływ czasu, a następnie wysyła aktualny czas // na zwrócony kanał. // Jest odpowiednikiem NewTimer (d) .C. // Bazowy Timer nie jest odzyskiwany przez moduł wyrzucania elementów bezużytecznych // dopóki czasomierz nie zostanie uruchomiony. Jeśli problemem jest wydajność, użyj NewTimer `` A co z tym stwierdzeniem:If efficiency is a concern, use NewTimer
lee
23

Jeśli nie przejmujesz się przesuwaniem tików (w zależności od tego, jak długo trwało to poprzednio przy każdym wykonaniu) i nie chcesz korzystać z kanałów, to możesz skorzystać z natywnej funkcji range.

to znaczy

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

Plac zabaw

Alekc
źródło
19

Sprawdź tę bibliotekę: https://github.com/robfig/cron

Przykład jak poniżej:

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
źródło
3

Szersza odpowiedź na to pytanie może obejmować podejście z klocków Lego, często używane w Occam i oferowane społeczności Java za pośrednictwem JCSP . Peter Welch przedstawia bardzo dobrą prezentację tego pomysłu.

To podejście typu plug-and-play przekłada się bezpośrednio na Go, ponieważ Go wykorzystuje te same podstawy komunikacyjnego procesu sekwencyjnego, co Occam.

Tak więc, jeśli chodzi o projektowanie powtarzalnych zadań, możesz zbudować swój system jako sieć przepływu danych składającą się z prostych komponentów (takich jak gorutyny), które wymieniają zdarzenia (tj. Komunikaty lub sygnały) kanałami.

Podejście to ma charakter kompozycyjny: każda grupa małych komponentów może sama zachowywać się jak większy komponent w nieskończoność. Może to być bardzo potężne, ponieważ złożone systemy współbieżne są zbudowane z łatwych do zrozumienia cegieł.

Przypis: w prezentacji Welcha używa on składni Occam dla kanałów, czyli ! i ? a te bezpośrednio odpowiadają ch <- i <-ch w Go.

Rick-777
źródło
3

Używam następującego kodu:

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

Jest to prostsze i dla mnie działa dobrze.

Gustavo Emmel
źródło
0

Jeśli chcesz to zatrzymać w dowolnym momencie ticker

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

Jeśli nie chcesz, aby zatrzymać go zaznaczyć :

tick := time.Tick(500 * time.Millisecond)
for range tick {
    fmt.Println("Tick")
}
John Balvin Arias
źródło