Aralıklarla tekrar eden görevleri yapmanın bir yolu var mı?


152

Go'da tekrarlayan arka plan görevlerini yapmanın bir yolu var mı? Timer.schedule(task, delay, period)Java'daki gibi bir şey düşünüyorum . Bunu bir gorutinle yapabileceğimi biliyorum Time.sleep()ama kolayca durdurulabilecek bir şey istiyorum.

İşte bulduğum şey, ama bana çirkin görünüyor. Daha temiz / daha iyi bir yol var mı?

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
Örneğinizde time.Duration (x) kullandığınız için teşekkür ederiz. Bulabildiğim her örnekte sabit kodlanmış bir int var ve bir int (veya float) değişken kullandığınızda şikayet ediyor.
Mike Graf

@MikeGraf Yapabileceğiniz t := time.Tick(time.Duration(period) * time.Second)dönemi olduğuint
florianrosenberg

bu çözüm oldukça iyi görünüyor, IMO. özellikle dış zaman yerine sadece f () çağırırsanız. tutarlı bir aralıkta iş bittikten x saniye sonra çalışmak istediğiniz durumlar için harika.
Luke W

Yanıtlar:


246

İşlev time.NewTicker, periyodik bir mesaj gönderen bir kanal oluşturur ve onu durdurmanın bir yolunu sağlar. Bunun gibi bir şey kullanın (test edilmemiş):

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

Sen kapatarak işçiyi durdurabilirsiniz quitkanalı: close(quit).


10
OP'nin ne istediğine bağlı olarak cevap yanlıştır. OP, çalışanın ne kadar zaman kullandığına bakılmaksızın periyodik bir yürütme istiyorsa do stuff, bir go rutininde çalıştırmanız gerekir, aksi takdirde bir sonraki işçi hemen çalıştırır (5 saniyeden daha fazlasına ihtiyaç duyduğunda).
nemo

2
IMO, close(quit)planlayıcıyı durdurmak istediğinizde yapmalısınız .
Dustin

3
Ticker'ı durdurmak işe yarıyor, ancak gorutin asla çöp toplanmayacak.
Paul Hankin

4
Bkz @SteveBrisk doc . Kanal kapalıysa, okuma başarılı olur ve bunu istemeyebilirsiniz.
nemo

10
@ bk0, zaman kanalları "yedeklenmez" (dokümantasyon "Yavaş alıcıları telafi etmek için aralıkları ayarlar veya onay işaretlerini bırakır"). Verilen kod tam olarak ne söylediğinizi yapar (en fazla bir görevde çalışır); görev uzun sürerse, bir sonraki çağrı basitçe ertelenir; muteks gerekmez. Bunun yerine, her aralıkta yeni bir görevin başlaması isteniyorsa (önceki tamamlanmasa bile), o zaman sadece kullanın go func() { /*do stuff */ }().
Dave C

26

Gibi bir şeye ne dersin

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

Oyun alanı


3
A time.Ticker, time.Aftergörevi zamanlamaya göre tutmayı tercih ettiğiniz yerden, yürütmeler arasında keyfi bir boşluktan daha iyidir .
Dustin

6
@Dustin Ve görevlerin sonu ile başlangıcı arasında sabit bir boşluk bırakarak iş yapmak istiyorsanız bu daha iyidir. İkisi de en iyisi değil - iki farklı kullanım durumu.
nos

`` // Sonra, sürenin geçmesini bekler ve sonra dönülen kanala // güncel saati gönderir. // NewTimer (d) .C ile eşdeğerdir. // Zamanlayıcı çalışana kadar, temeldeki Zamanlayıcı çöp toplayıcı tarafından // kurtarılmaz. Verimlilik önemliyse, NewTimer'ı kullanın `` Bu ifadeye ne dersiniz:If efficiency is a concern, use NewTimer
lee

25

Kene kaydırmayı umursamıyorsanız (her yürütmede daha önce ne kadar sürdüğüne bağlı olarak) ve kanalları kullanmak istemiyorsanız, yerel aralık işlevini kullanmak mümkündür.

yani

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

Oyun alanı


19

Bu kitaplığa göz atın: https://github.com/robfig/cron

Aşağıdaki gibi örnek:

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

Bu soruya daha geniş bir cevap, Occam'da sıklıkla kullanılan ve Java topluluğuna JCSP aracılığıyla sunulan Lego tuğla yaklaşımını düşünebilir . Bu fikir hakkında Peter Welch'in çok güzel bir sunumu var .

Bu tak ve çalıştır yaklaşımı doğrudan Go'ya çevrilir, çünkü Go, Occam'ın yaptığı gibi aynı Sıralı İletişim Süreci temellerini kullanır.

Dolayısıyla, tekrarlayan görevlerin tasarlanması söz konusu olduğunda, sisteminizi, kanallar aracılığıyla olayları (yani mesajlar veya sinyaller) değiş tokuş eden basit bileşenlerden (grup olarak) oluşan bir veri akışı ağı olarak oluşturabilirsiniz.

Bu yaklaşım bileşimseldir: her küçük bileşen grubu, daha büyük bir bileşen, sonsuza kadar davranabilir. Bu çok güçlü olabilir çünkü karmaşık eşzamanlı sistemler anlaşılması kolay tuğlalardan yapılmıştır.

Dipnot: Welch'in sunumunda, kanallar için Occam sözdizimini kullanıyor, yani ! ve ? ve bunlar doğrudan Go'daki ch <- ve <-ch'e karşılık gelir .


3

Aşağıdaki kodu kullanıyorum:

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

Daha basit ve benim için iyi çalışıyor.


0

Herhangi bir anda durdurmak istiyorsanız, ticker

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

Durdurmak istemiyorsanız işaretleyin :

tick := time.Tick(500 * time.Millisecond)
for range tick {
    fmt.Println("Tick")
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.