Có cách nào để thực hiện các nhiệm vụ lặp đi lặp lại trong khoảng thời gian không?


148

Có cách nào để thực hiện các tác vụ nền lặp đi lặp lại trong Go không? Tôi đang nghĩ về một cái gì đó giống như Timer.schedule(task, delay, period)trong Java. Tôi biết tôi có thể làm điều này với một con goroutine và Time.sleep(), nhưng tôi muốn thứ gì đó dễ dàng dừng lại.

Đây là những gì tôi có, nhưng trông xấu xí đối với tôi. Có cách nào sạch hơn / tốt hơn không?

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
Cảm ơn bạn đã sử dụng time.Duration (x) trong ví dụ của bạn. Mỗi ví dụ tôi có thể tìm thấy có một int mã hóa cứng và nó phàn nàn khi bạn sử dụng một vars int (hoặc float).
Mike Graf

@MikeGraf bạn có thể làm t := time.Tick(time.Duration(period) * time.Second)trong khoảng thời gian là mộtint
florianrosenberg

Giải pháp này có vẻ khá tốt, IMO. đặc biệt nếu bạn chỉ cần gọi f () thay vì thời gian bên ngoài.AfterFunc. tuyệt vời cho trường hợp bạn muốn thực hiện công việc x giây sau khi hoàn thành công việc, so với khoảng thời gian nhất quán.
Luke W

Câu trả lời:


240

Hàm này time.NewTickertạo một kênh gửi thông điệp định kỳ và cung cấp một cách để ngăn chặn nó. Sử dụng nó một cái gì đó như thế này (chưa được kiểm tra):

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

Bạn có thể ngừng người lao động bằng cách đóng quitkênh: close(quit).


9
Câu trả lời là không chính xác tùy thuộc vào những gì OP muốn. Nếu OP muốn thực thi định kỳ bất kể nhân viên sử dụng bao nhiêu thời gian, bạn sẽ phải chạy do stufftheo thói quen đi nếu không nhân viên tiếp theo sẽ thực thi ngay lập tức (khi cần hơn 5 giây).
nemo

2
IMO, bạn chỉ nên close(quit)khi bạn muốn dừng lịch trình.
Dustin

3
Dừng ticker hoạt động, nhưng goroutine sẽ không bao giờ được thu gom rác.
Paul Hankin

4
@SteveBrisk Xem tài liệu . Nếu kênh bị đóng, việc đọc sẽ thành công và bạn có thể không muốn điều đó.
nemo

10
@ bk0, các kênh thời gian không "sao lưu" (tài liệu có nội dung "Nó điều chỉnh các khoảng hoặc giảm dấu tích để bù cho các máy thu chậm"). Mã đã cho thực hiện chính xác những gì bạn nói (chạy nhiều nhất một nhiệm vụ); nếu nhiệm vụ mất nhiều thời gian thì lần gọi tiếp theo sẽ bị trì hoãn; không cần mutex. Nếu thay vào đó, mong muốn rằng một nhiệm vụ mới được bắt đầu mỗi khoảng thời gian (ngay cả khi trước đó chưa kết thúc) thì chỉ cần sử dụng go func() { /*do stuff */ }().
Dave C

26

Thế còn một cái gì đó như

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

Sân chơi


3
A time.Tickertốt hơn so với time.Afternơi bạn muốn giữ nhiệm vụ đúng tiến độ so với khoảng cách tùy ý giữa các lần thực thi.
Dustin

5
@Dustin Và điều này tốt hơn nếu bạn muốn thực hiện công việc với một khoảng cách cố định giữa kết thúc và bắt đầu nhiệm vụ. Không phải là tốt nhất - đó là hai trường hợp sử dụng khác nhau.
số

`` `// Sau khi đợi thời lượng trôi qua và sau đó gửi thời gian hiện tại // trên kênh được trả về. // Nó tương đương với NewTimer (d) .C. // Bộ định thời bên dưới không được bộ thu gom rác thu hồi // cho đến khi bộ định thời kích hoạt. Nếu hiệu quả là một mối quan tâm, hãy sử dụng NewTimer `` `Làm thế nào về tuyên bố này:If efficiency is a concern, use NewTimer
lee

23

Nếu bạn không quan tâm đến việc dịch chuyển tick (tùy thuộc vào thời gian trước đó trong mỗi lần thực hiện) và bạn không muốn sử dụng các kênh, bạn có thể sử dụng chức năng phạm vi gốc.

I E

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

Sân chơi


19

Kiểm tra thư viện này: https://github.com/robfig/cron

Ví dụ như dưới đây:

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

Một câu trả lời rộng hơn cho câu hỏi này có thể xem xét cách tiếp cận gạch Lego thường được sử dụng trong Occam và được cung cấp cho cộng đồng Java thông qua JCSP . Có một bài thuyết trình rất hay của Peter Welch về ý tưởng này.

Cách tiếp cận plug-and-play này dịch trực tiếp sang Go, bởi vì Go sử dụng các nguyên tắc cơ bản Giao tiếp liên tục tương tự như Occam.

Vì vậy, khi nói đến việc thiết kế các tác vụ lặp đi lặp lại, bạn có thể xây dựng hệ thống của mình dưới dạng một mạng dữ liệu gồm các thành phần đơn giản (như goroutines) để trao đổi các sự kiện (tức là tin nhắn hoặc tín hiệu) qua các kênh.

Cách tiếp cận này là thành phần: mỗi nhóm các thành phần nhỏ có thể tự hoạt động như một thành phần lớn hơn, ad infinitum. Điều này có thể rất mạnh mẽ vì các hệ thống đồng thời phức tạp được làm từ những viên gạch dễ hiểu.

Chú thích: trong bài thuyết trình của Welch, anh ấy sử dụng cú pháp Occam cho các kênh, đó là ! ? và những cái này tương ứng trực tiếp với ch <-<-ch trong Go.


3

Tôi sử dụng mã sau đây:

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

Nó đơn giản hơn và hoạt động tốt với tôi.


0

Nếu bạn muốn ngăn chặn nó trong bất kỳ thời điểm ticker

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

Nếu bạn không muốn dừng nó, hãy đánh dấu :

tick := time.Tick(500 * time.Millisecond)
for range tick {
    fmt.Println("Tick")
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.