1. ホーム
  2. ゴラン

Golang Cronタイムドタスク解析

2022-03-01 12:50:35
<パス

1. cron式の基本フォーマット
linuxを使ったことがある人なら、cronについて知っているはずです。crontab -eを使えば、linuxの時間指定タスクを設定することができます。しかし、linuxのcronは分単位までしか正確ではありません。ここで議論しているcronのGo実装は秒単位の精度が可能ですが、この大きな違いを除けば、cron式の基本構文は類似しています。(JavaでQuartzを使ったことがある人は、cron式に慣れていると思いますが、これも秒単位の精度を持つという点で、ここで説明するGo cronに似ています)

cron (scheduled task)はその名の通り、特定のタスク(ジョブ)を合意した時間に実行する。cron式はこの合意を表す。

cron式は時間の集まりを表し、スペースで区切られた6つのフィールドで表現されます。

フィールド名 必須か否か 値を許可するか否か 特定の文字
秒数 Yes 0-59
* / , -
分(Minutes)は0~59
* / , -
時間(アワー)は0~23
* / , -
月の日数は1~31日
* / , - ?
月(Month)は1~12またはJAN-DEC
* / , -
曜日(Day of Week) No 0~6 または SUM-SAT
* / , - ?
注意事項
1) 月(Month)と曜日(Day of week)のフィールドの値は、大文字と小文字を区別しない。例えば、SUN, Sun, sun は同じ値である。
2) 週の日数
(曜日)フィールドを提供しない場合、*と同等となります。

2. 特殊文字の記述
1) アスタリスク (*)
cron式がフィールド内のすべての値に一致することを示す。例えば、5番目のフィールドにアスタリスク(月)を使用すると、各月を示すことができます。

2) スラッシュ (/)
は成長間隔を示し、例えば最初のフィールド(分)の値が3-59/15であれば、毎時3分に実行を開始し、その後15分ごと(つまりこれらの時点では3、18、33、48)に実行することを意味し、ここでは次のようにも表現できる。3/15

3) カンマ(,)
例えば、6番目のフィールドの値は MON,WED,FRI であり、これは月曜日、水曜日、金曜日を意味する 実行

4) ハイフネーション (-)
範囲を示す。例:第3フィールドの値が9-17の場合、9時と17時を含む1時間あたりの直接の午前9時から午後5時までを意味する。

5) クエスチョンマーク(?)
値が指定されていないことを示し、*の代わりに使用することができます。

3. 主な型またはインターフェースの説明
1) Cron: 実行される一連のエンティティを含む; pause [stop] をサポートする; エンティティを追加する、など。

type Cron struct {
    entries []*Entry
    stop chan struct {} // Control Cron instances to pause
    add chan *Entry // When Cron is already running, adding a new Entity is done via the add channel
    snapshot chan []*Entry // Get a snapshot of all current entities
    running bool // true when already running; false otherwise
}

Cron構造体は、メンバーをエクスポートしないことに注意してください。

なお、struct{}型にはstopというメンバが1つあり、これは空の構造体です。

2) エントリー:スケジューリングエンティティ

type Entry struct {
    // The schedule on which this job should be run.
    // Responsible for scheduling the execution of the Job in the current Entity
    Schedule Schedule

    // The next time the job will run. This is the zero time if Cron has not been
    // started or this entry's schedule is unsatisfiable
    // The next time the Job will run
    Next time.

    // The last time this job was run. This is the zero time if the job has never
    // been run.
    // The last time this job was run.
    Prev time.

    // The Job to run.
    // The Job to run.
    Job Job
}

3) ジョブ。各エンティティには、実行が必要なジョブが1つ含まれています。
これは、runというメソッドを1つ持つインターフェースです。

type Job interface {
    Run()
}

Entity では Job 型が必須なので、定期的に実行したいタスクのために Job インターフェイスを実装する必要があります。また、Job インターフェースはパラメータも戻り値もないメソッドを 1 つだけ持っているので、使いやすいように、著者らは型

type FuncJob func()



Run()メソッドを実装するだけで、Jobインタフェースを実装しています。

func (f FuncJob) Run() { f() }



このように、引数も戻り値もない関数は、AddFuncメソッドが行うFuncJobへの型変換を強制することで、Jobとして利用することができます。

4) スケジュール:各エンティティにはスケジューラ(Schedule)が含まれる。
は、ジョブの実行をスケジューリングする役割を担っている。また、インターフェースでもある。

type Schedule interface {
    // Return the next activation time, later than the given time.
    // Next is invoked initially, and then each time the job is run.
    // Return the next activation time of a Job in the same Entity
    Next(time.Time) time.
}

Scheduleの具体的な実装は、Cron式をパースすることで得られます。

スケジュールの具体的な実装として、SpecScheduleとConstantDelayScheduleの2つがライブラリで提供されています。

スペックスケジュール

type SpecSchedule struct {
    Second, Minute, Hour, Dom, Month, Dow uint64
}

冒頭で紹介したCron式により、各フィールドの意味を簡単に知ることができ、様々な式を解析することで、最終的にSpecScheduleのインスタンスが生成されます。ライブラリ内のParseはSpecScheduleのインスタンスを返します(もちろんScheduleインタフェースを実装しています)。

コンスタントディレイスケジュール

type ConstantDelaySchedule struct {
    Delay time.Duration // The time interval of the loop
}

これは単純なループスケジューラで、例えば5分ごとなどです。最小単位は秒であり、ミリ秒のような秒未満の単位はないことに注意してください。

Every関数により、この型のインスタンスを取得することができます(例)。

constDelaySchedule := Every(5e9)



5秒ごとに実行されるスケジューラを取得します。

4. 主なインスタンス化メソッド
1) 関数
Cron のインスタンス化

func New() *Cron {
    return &Cron{
        entries: nil,
        add: make(chan *Entry),
        stop: make(chan struct{}),
        snapshot: make(chan []*Entry),
        running: false,
    }
}

は、メンバーが基本的にデフォルト値を使用してインスタンス化されていることを確認します。

Cron式のパース

func Parse(spec string) (_ Schedule, err error)



specは可能です。

Full crontab specs, e.g. "* * * * * ?"
Descriptors, e.g. "@midnight", "@every 1h30m"


メンバーメソッド

// ジョブをCronに追加する
// 前述のように、このメソッドは単に FuncJob 型による cmd 変換を強制し、AddJob メソッドを呼び出します。
func (c *Cron) AddFunc(spec string, cmd func()) error

// ジョブをCronに追加する
// スケジューラーインスタンス(Schedule)へのcron式specをParse関数で解析し、c.Scheduleメソッドを呼び出す。
func (c *Cron) AddJob(spec string, cmd Job) error

// 現在の Cron に含まれるすべてのエンティティのスナップショットを取得します。
func (c *Cron) Entries() []*Entry

// 2つの引数を持つエンティティをインスタンス化し、現在のCronに追加します。
// 注意:もし現在のCronが実行されていない場合、エンティティは直接Cronに追加されます。
// そうでない場合は、add member チャンネルを使用して実行中の Cron にエンティティを追加します。
func (c *Cron) Schedule(schedule Schedule, cmd Job)

// 現在のCronを実行するために新しいgoroutineを開始します。
func (c *Cron) Start()

// stopメンバにstruct{}{}を送信して現在のCronを停止し、runningをfalseにセットします。
// このことから、stopはCronが停止していることを通知するだけであることがわかるので、チャンネルに値を送るだけで十分であり、値が何であるかは気にしなくてよい
// そこで、メンバー stop は空の構造体として定義されます。

func (c *Cron) Stop()



5. 使用例

package main

import (
    "github.com/robfig/cron"
    "log"
)

func main() {
    i := 0
    c := cron.New()
    spec := "*/5 * * * * ? "
    c.AddFunc(spec, func() {
        i++
        log.Println("cron running:", i)
    })
    c.Start()
    select{}
}