一定の間隔で繰り返し作業を行う方法はありますか?


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があり、int(またはfloat)変数を使用すると文句を言います。
Mike Graf

@MikeGrafあなたはt := time.Tick(time.Duration(period) * time.Second)期間がどこであるかを行うことができますint
フロリアンローゼンバーグ

この解決策はかなり良いようです、IMO。特に uが外側のtime.AfterFuncの代わりに単にf()を呼び出す場合。一定の間隔ではなく、仕事が終わってから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
        }
    }
 }()

quitチャネルを閉じるとワーカーを停止できます:close(quit)


9
OPが何を望んでいるかによって、答えは正しくありません。OPがワーカーの使用時間に関係なく定期的に実行する必要がある場合はdo stuff、goルーチンで実行する必要があります。そうしないと、次のワーカーがすぐに実行されます(5秒以上必要な場合)。
nemo 2013年

2
IMO、close(quit)スケジューラを停止したいときだけにすべきです。
ダスティン2013年

3
ティッカーの停止は機能しますが、ゴルーチンはガベージコレクションされません。
ポールハンキン、2013年

4
@SteveBrisk ドキュメントを参照してください。チャネルが閉じている場合、読み取りは成功し、それが望ましくない場合があります。
nemo 2013年

10
@ bk0、タイムチャネルは「バックアップ」しません(ドキュメントには「遅いレシーバーを補うために間隔を調整するか、ティックをドロップする」と記載されています)。与えられたコードはあなたが言うことを正確に実行します(多くても1つのタスクを実行します)。タスクに長い時間がかかる場合、次の呼び出しは単に遅延します。ミューテックスは必要ありません。代わりに、新しいタスクが間隔ごとに開始されることが望まれる場合(前のタスクが終了していない場合でも)、単にを使用しますgo func() { /*do stuff */ }()
Dave 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.Tickertime.After、タスクをスケジュールどおりに保ちたい場合よりも、実行間の任意のギャップよりも優れています。
ダスティン2013年

5
@Dustinそして、これは、タスクの終わりと始まりの間に一定のギャップがある作業を実行したい場合に適しています。どちらも最適ではありません。2つの異なる使用例があります。
番号

`` `//期間が経過するのを待ってから、返されたチャネルで//現在の時刻を送信します。// NewTimer(d).Cと同等です。//基になるTimerは、タイマーが起動するまで、ガベージコレクターによって//回復されません。効率が懸念される場合は、使用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

この質問に対するより広い回答は、Occamでよく使用され、JCSPを介してJavaコミュニティに提供されるレゴブリックアプローチを検討する場合があります。このアイデアについては、Peter Welchによる非常に優れたプレゼンテーションがあります。

GoはOccamと同じCommunicating Sequential Processの基礎を使用するため、このプラグアンドプレイアプローチはGoに直接変換されます。

したがって、反復的なタスクの設計に関しては、チャネルを介してイベント(メッセージやシグナルなど)を交換する単純なコンポーネント(ゴルーチンなど)のデータフローネットワークとしてシステムを構築できます。

このアプローチは構成的です。小さなコンポーネントの各グループは、それ自体が無限に大きなコンポーネントとして動作できます。複雑な並行システムは、理解しやすいブリックで構成されているため、これは非常に強力です。

脚注:ウェルチのプレゼンテーションでは、チャンネルにOccam構文を使用していますこれらは、Goのch <-および<-chに直接対応しています。


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

それはもっとシンプルで、私にはうまくいきます。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.