Чи є спосіб робити повторювані завдання з інтервалом?


148

Чи є спосіб виконувати повторювані фонові завдання в програмі Go? Я думаю про щось на кшталт Timer.schedule(task, delay, period)Java. Я знаю, що я можу це зробити за допомогою програми і 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) vars.
Майк Граф

@MikeGraf ви можете робити t := time.Tick(time.Duration(period) * time.Second)там, де періодint
florianrosenberg

це рішення здається досить хорошим, ІМО. особливо якщо u просто зателефонуйте f () замість зовнішнього часу. відмінно підходить для тих випадків, коли ти хочеш виконати роботу х секунд після закінчення роботи, порівняно з послідовним інтервалом.
Лука Ш

Відповіді:


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
Відповідь невірна залежно від того, чого хоче ОП. Якщо ОП хоче періодичне виконання незалежно від того, скільки часу використовує працівник, вам доведеться бігати do stuffв ході роботи або ж наступний працівник виконає негайно (коли потрібно більше 5 секунд).
немо

2
ІМО, вам слід просто close(quit)тоді, коли ви хочете зупинити планувальник.
Дастін

3
Припинення тикера працює, але у програмі ніколи не збирається сміття.
Пол Ханкін

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 І це краще, якщо ви хочете виконати роботу з фіксованим проміжком між кінцем і початком завдань. Не найкраще - це два різні випадки використання.
нос

`` // Після чекає, поки триває тривалість часу, а потім надсилає поточний час // на повернений канал. // Це еквівалентно NewTimer (d) .C. // Базовий таймер не відновлюється сміттєзбірником // до тих пір, поки таймер не запуститься. Якщо ефективність є проблемою, використання NewTimer `` `Як щодо цього твердження:If efficiency is a concern, use NewTimer
підвітряного

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

Більш широка відповідь на це питання може розглянути підхід Lego цегли, який часто використовується в Occam, і запропонований спільноті Java через JCSP . Є дуже гарна презентація Пітера Велча щодо цієї ідеї.

Цей підхід підключення та перетворення безпосередньо перекладається на Go, оскільки Go використовує ті ж основи комунікаційного послідовного процесу, що й Occam.

Отже, якщо мова йде про розробку задач, що повторюються, ви можете побудувати свою систему як мережу потоку даних з простих компонентів (наприклад, програм), які обмінюються подіями (тобто повідомленнями або сигналами) по каналах.

Цей підхід є композиційним: кожна група невеликих компонентів може поводити себе як більший компонент, ad infinitum. Це може бути дуже потужним, оскільки складні паралельні системи виготовляються з легко зрозумілих цеглин.

Виноска: у презентації Welch він використовує синтаксис Occam для каналів, який є ! і ? і вони безпосередньо відповідають ch <- і <-ch в Go.


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")
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.