มีวิธีการทำงานซ้ำ ๆ เป็นระยะ ๆ หรือไม่?


148

มีวิธีการทำงานพื้นหลังซ้ำ ๆ ใน Go หรือไม่? ฉันกำลังคิดถึงบางสิ่งที่เหมือนTimer.schedule(task, delay, period)ใน Java ฉันรู้ว่าฉันสามารถทำได้ด้วย goroutine และ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 hardcoded และมันบ่นเมื่อคุณใช้ vars int (หรือลอย)
Mike Graf

@MikeGraf คุณสามารถทำได้ในt := time.Tick(time.Duration(period) * time.Second)ช่วงเวลาที่เป็นint
florianrosenberg

วิธีนี้ดูดีทีเดียว IMO ESP ถ้าหากคุณเพียงแค่โทร f () แทนเวลาด้านนอก AfterFunc เหมาะอย่างยิ่งสำหรับกรณีที่คุณต้องการทำงาน x วินาทีหลังจากเสร็จสิ้นงานและในช่วงเวลาที่สอดคล้องกัน
ลุค W

คำตอบ:


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

คุณสามารถหยุดการปฏิบัติงานโดยวิธีปิดช่อง:quitclose(quit)


9
คำตอบนั้นไม่ถูกต้องขึ้นอยู่กับสิ่งที่ OP ต้องการ หาก OP ต้องการดำเนินการเป็นครั้งคราวโดยไม่คำนึงว่าพนักงานใช้เวลาเท่าใดคุณจะต้องทำงานdo stuffในกิจวัตรการทำงานไม่เช่นนั้นผู้ปฏิบัติงานคนต่อไปจะดำเนินการทันที (เมื่อต้องการมากกว่า 5 วินาที)
nemo

2
IMO คุณควรclose(quit)หยุดเมื่อคุณต้องการที่จะหยุดกำหนดการ
ดัสติน

3
การหยุดทำงานของทิกเกอร์ แต่ goroutine จะไม่ถูกเก็บขยะ
Paul Hankin

4
@SteveBrisk ดูเอกสาร หากช่องถูกปิดการอ่านจะประสบความสำเร็จและคุณอาจไม่ต้องการ
nemo

10
@ bk0, ช่องเวลาไม่ "สำรองข้อมูล" (เอกสารประกอบบอกว่า "จะปรับช่วงเวลาหรือทำเครื่องหมายเห็บเพื่อทำให้เครื่องรับช้า") รหัสที่ได้รับนั้นทำในสิ่งที่คุณพูดอย่างแน่นอน หากภารกิจใช้เวลานานการร้องขอครั้งต่อไปจะล่าช้า ไม่จำเป็นต้องใช้ mutex แต่ถ้ามันเป็นที่ต้องการว่างานใหม่รับการเริ่มต้นทุกช่วงเวลา (แม้ว่าก่อนหน้านี้ยังไม่เสร็จสิ้น) 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
lee

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

คำตอบที่กว้างขึ้นสำหรับคำถามนี้อาจพิจารณาแนวทางอิฐเลโก้มักจะใช้ในสาธารณรัฐโคลัมเบียและเสนอขายให้แก่ชุมชน Java ผ่านJCSP มีการนำเสนอที่ดีมากโดย Peter Welchในความคิดนี้

วิธีการแบบ plug-and-play นี้แปลโดยตรงไปที่เพราะไปใช้พื้นฐานการสื่อสารตามลำดับกระบวนการสื่อสารเช่นเดียวกับที่เกิดขึ้น

ดังนั้นเมื่อมันมาถึงการออกแบบงานซ้ำ ๆ คุณสามารถสร้างระบบของคุณเป็นเครือข่ายดาต้าโฟลว์ขององค์ประกอบง่ายๆ (เช่น goroutines) ที่แลกเปลี่ยนเหตุการณ์ (เช่นข้อความหรือสัญญาณ) ผ่านช่องทาง

วิธีการนี้เป็นองค์ประกอบ: แต่ละกลุ่มขององค์ประกอบขนาดเล็กสามารถทำตัวเป็นองค์ประกอบที่ใหญ่กว่าได้ สิ่งนี้มีประสิทธิภาพมากเพราะระบบที่ซับซ้อนพร้อมกันนั้นทำจากอิฐที่เข้าใจง่าย

เชิงอรรถ: ในการนำเสนอของ Welch เขาใช้ไวยากรณ์ Occam สำหรับช่องทางซึ่งก็คือ! และ? และสิ่งเหล่านี้ตรงกับch <-และ<-ch in 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")
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.