Go語言定時(shí)任務(wù)的實(shí)現(xiàn)示例
robfig/cron 是Go語言實(shí)現(xiàn)的開源定時(shí)任務(wù)調(diào)度框架,核心代碼是巧妙的使用chan + select + for實(shí)現(xiàn)了一個(gè)輕量
級(jí)調(diào)度協(xié)程,不但語法簡(jiǎn)潔,而且具有很好的性能。
Cron是Go中用于設(shè)置定時(shí)任務(wù)的一個(gè)庫(kù),需要注意的是,Cron庫(kù)分兩個(gè)大版本,v1.2和v3.0,其功能和go get地
址都是不同的,注意區(qū)分。
v1.2官方文檔:https://pkg.go.dev/github.com/robfig/cron
v3官方文檔:https://pkg.go.dev/github.com/robfig/cron/v3
cron github倉(cāng)庫(kù):https://github.com/robfig/cron
1、安裝依賴
# v1.2 go get github.com/robfig/cron # v3 go get github.com/robfig/cron/v3@v3.0.0
2、定時(shí)任務(wù)簡(jiǎn)單案例
package main
import (
?? ?"fmt"
?? ?"github.com/robfig/cron/v3"
?? ?"time"
)
func main() {
?? ?// 新建一個(gè)定時(shí)任務(wù)對(duì)象,根據(jù)cron表達(dá)式進(jìn)行時(shí)間調(diào)度,cron可以精確到秒,大部分表達(dá)式格式也是從秒開始
?? ?// 默認(rèn)從分開始進(jìn)行時(shí)間調(diào)度
?? ?// cronTab := cron.New()
?? ?// 精確到秒
?? ?cronTab := cron.New(cron.WithSeconds())
?? ?// 定義定時(shí)器調(diào)用的任務(wù)函數(shù)
?? ?task := func() {
?? ??? ?fmt.Println("hello world", time.Now())
?? ?}
?? ?// 定時(shí)任務(wù),cron表達(dá)式,每五秒一次
?? ?spec := "*/5 * * * * ?"
?? ?// 添加定時(shí)任務(wù)
?? ?cronTab.AddFunc(spec, task)
?? ?// 啟動(dòng)定時(shí)器
?? ?cronTab.Start()
?? ?// 阻塞主線程停止
?? ?select {}
}# 輸出信息
hello world 2023-05-30 12:48:40.0089132 +0800 CST m=+0.087419701
hello world 2023-05-30 12:48:45.0040694 +0800 CST m=+5.082575901
hello world 2023-05-30 12:48:50.001667 +0800 CST m=+10.080173501
hello world 2023-05-30 12:48:55.0013075 +0800 CST m=+15.079814001
hello world 2023-05-30 12:49:00.0011284 +0800 CST m=+20.079634901
hello world 2023-05-30 12:49:05.0080655 +0800 CST m=+25.086572001
......
3、Cron表達(dá)式
cron 表達(dá)式是一個(gè)好東西,這個(gè)東西不僅 Java 的 quartZ 能用到,Go 語言中也可以用到。
Linux 也是可以用 crontab -e 命令來配置定時(shí)任務(wù)。
Go 語言和 Java 中都是可以精確到秒的,但是 Linux 中不行。
cron表達(dá)式代表一個(gè)時(shí)間的集合,使用6個(gè)空格分隔的字段表示:
| 字段名 | 是否必須 | 允許的值 | 允許的特定字符 |
|---|---|---|---|
| 秒(Seconds) | 是 | 0-59 | * / , - |
| 分(Minute) | 是 | 0-59 | * / , - |
| 時(shí)(Hours) | 是 | 0-23 | * / , - |
| 日(Day of month) | 是 | 1-31 | * / , - ? |
| 月(Month) | 是 | 1-12 或 JAN-DEC | * / , - |
| 星期(Day of week) | 否 | 0-6 或 SUM-SAT | * / , - ? |
特殊字符說明:
? 只能在 day 和 week 中使用,標(biāo)識(shí)未說明的值,用以解決 day 和 week 的沖突,比如 * * * 10 * ? 表示每
月10號(hào)觸發(fā),而換成 * 則表示不管星期幾都可觸發(fā),與前者發(fā)生沖突。
3.1 Cron表達(dá)式說明
- 月(Month)和星期(Day of week)字段的值不區(qū)分大小寫,如:SUN、Sun 和 sun 是一樣的。
- 星期(Day of week)字段如果沒提供,相當(dāng)于是 *
3.2 Cron表達(dá)式示例說明
如果我們使用 crontab := cron.New(cron.WithSeconds()),我們的定時(shí)任務(wù)表達(dá)式需要為:
* * * * * *
如果我們使用 cronTab := cron.New(),我們的定時(shí)任務(wù)表達(dá)式需要為:
* * * * *,不包含秒
這 6 個(gè) * 分別代表什么意思呢?
# 第一個(gè)*: second,范圍(0 - 60)
# 第二個(gè)*: min,范圍(0 - 59)
# 第三個(gè)*: hour,范圍(0 - 23)
# 第四個(gè)*: day of month,范圍(1 - 31)
# 第五個(gè)*: month,范圍(1 - 12)
# 第六個(gè)*: day of week,范圍(0 - 6) (0 to 6 are Sunday to Saturday)
* * * * * *
3.3 cron特定字符說明
| 符號(hào) | 說明 |
|---|---|
| (*) | 表示 cron 表達(dá)式能匹配該字段的所有值。如在第5個(gè)字段使用星號(hào)(month),表示每個(gè)月 |
| (/) | 表示增長(zhǎng)間隔,如第1個(gè)字段(minutes) 值是 3-59/15,表示每小時(shí)的第3分鐘開始執(zhí)行一次,之后每隔 15 分鐘執(zhí)行一次(即 3、18、33、48 這些時(shí)間點(diǎn)執(zhí)行),這里也可以表示為:3/15 |
| (,) | 用于枚舉值,如第6個(gè)字段值是 MON,WED,FRI,表示 星期一、三、五 執(zhí)行 |
| (-) | 表示一個(gè)范圍,如第3個(gè)字段的值為 9-17 表示 9am 到 5pm 直接每個(gè)小時(shí)(包括9和17) |
| (?) | 只用于 日(Day of month) 和 星期(Day of week),表示不指定值,可以用于代替 * |
3.4 常用cron舉例
每隔5秒執(zhí)行一次:*/5 * * * * ?
每隔1分鐘執(zhí)行一次:0 */1 * * * ?
每天23點(diǎn)執(zhí)行一次:0 0 23 * * ?
每天凌晨1點(diǎn)執(zhí)行一次:0 0 1 * * ?
每月1號(hào)凌晨1點(diǎn)執(zhí)行一次:0 0 1 1 * ?
每周一和周三晚上22:30: 00 30 22 * * 1,3
在26分、29分、33分執(zhí)行一次:0 26,29,33 * * * ?
每天的0點(diǎn)、13點(diǎn)、18點(diǎn)、21點(diǎn)都執(zhí)行一次:0 0 0,13,18,21 * * ?
每年三月的星期四的下午14:10和14:40: 00 10,40 14 ? 3 4
3.5 預(yù)定義的時(shí)間格式
您可以使用幾個(gè)預(yù)定義的表達(dá)式來代替上表的表達(dá)式,使用如下:
| 輸入 | 描述 | 等式 |
|---|---|---|
@yearly (or @annually) | 每年一次,1月1日午夜 | 0 0 0 1 1 * |
@monthly | 每月運(yùn)行一次,每月第一天午夜 | 0 0 0 1 * * |
@weekly | 每周運(yùn)行一次,周六/周日之間的午夜 | 0 0 0 * * 0 |
@daily (or @midnight) | 每天午夜運(yùn)行一次 | 0 0 0 * * * |
@hourly | 每小時(shí)運(yùn)行一次 | 0 0 * * * * |
還可以安排作業(yè)以固定的間隔執(zhí)行,從添加作業(yè)或運(yùn)行cron時(shí)開始。這是通過如下格式化cron規(guī)范來支持的:
@every <duration>
// 例如
c := cron.New()
c.AddFunc("@every 1h30m", func() { fmt.Println("Every hour thirty, starting an hour thirty from now") })4、時(shí)區(qū)
默認(rèn)情況下,所有時(shí)間都是基于當(dāng)前時(shí)區(qū)的,也可自定義:在時(shí)間字符串前面添加一個(gè)CRON_TZ= + 具體時(shí)區(qū)
一些常用的時(shí)區(qū):
- 東京時(shí)區(qū):Asia/Tokyo
- 紐約時(shí)區(qū):America/New_York
- 上海時(shí)區(qū):Asia/Shanghai
- 香港時(shí)區(qū):Asia/Hong_Kong
創(chuàng)建cron對(duì)象時(shí)增加一個(gè)時(shí)區(qū)選項(xiàng)cron.WithLocation(location),location為time.LoadLocation(zone)加載的時(shí)區(qū)
對(duì)象,zone為具體的時(shí)區(qū)格式。
package main
import (
?? ?"fmt"
?? ?"github.com/robfig/cron/v3"
?? ?"time"
)
func main() {
?? ?//直接配置時(shí)區(qū)
?? ?nyc, _ := time.LoadLocation("America/New_York")
?? ?// cron.New(cron.WithLocation(time.UTC))
?? ?c := cron.New(cron.WithLocation(nyc),cron.WithSeconds())
?? ?c.AddFunc("*/5 * * * * ?", func() {
?? ??? ?fmt.Println("Every 5 second at New York")
?? ?})
?? ?// 參數(shù)里面配置時(shí)區(qū)
?? ?c.AddFunc("CRON_TZ=Asia/Tokyo */5 * * * * ?", func() {
?? ??? ?fmt.Println("Every 5 second at Tokyo")
?? ?})
?? ?c.Start()
?? ?select {}
}# 輸出
Every 5 second at New York
Every 5 second at Tokyo
Every 5 second at Tokyo
Every 5 second at New York
......
5、自定義定時(shí)任務(wù)以及多個(gè)定時(shí)任務(wù)
自定義定時(shí)任務(wù)只需要實(shí)現(xiàn)Job接口的Run方法。
package main
import (
?? ?"fmt"
?? ?"github.com/robfig/cron/v3"
?? ?"time"
)
type Task1 struct {
?? ?Name string
}
// 自定義定時(shí)任務(wù)只需要實(shí)現(xiàn)Job接口的Run方法
func (t *Task1) Run() {
?? ?fmt.Println("Task1: ", t.Name)
}
type Task2 struct {
?? ?Name string
}
// 自定義定時(shí)任務(wù)只需要實(shí)現(xiàn)Job接口的Run方法
func (t *Task2) Run() {
?? ?fmt.Println("Task2: ", t.Name)
}
func main() {
?? ?cronTab := cron.New(cron.WithSeconds())
?? ?// 定義定時(shí)器調(diào)用的任務(wù)函數(shù)
?? ?// 定時(shí)任務(wù)
?? ?// cron表達(dá)式,每五秒一次
?? ?spec := "*/5 * * * * ?"
?? ?//定義定時(shí)器調(diào)用的任務(wù)函數(shù)
?? ?task := func() {
?? ??? ?fmt.Println("hello world", time.Now())
?? ?}
?? ?// 添加多個(gè)定時(shí)器
?? ?cronTab.AddFunc(spec, task)
?? ?cronTab.AddJob(spec, &Task1{Name: "tom"})
?? ?cronTab.AddJob(spec, &Task2{Name: "merry"})
?? ?// 啟動(dòng)定時(shí)器
?? ?cronTab.Start()
?? ?// 關(guān)閉,但是不能關(guān)閉已經(jīng)在執(zhí)行中的任務(wù)
?? ?defer cronTab.Stop()
?? ?// 阻塞主線程停止
?? ?select {}
}# 輸出
Task1: tom
Task2: merry
hello world 2023-05-30 15:03:55.006422 +0800 CST m=+1.424751701
Task2: merry
Task1: tom
hello world 2023-05-30 15:04:00.0057737 +0800 CST m=+6.424103401
Task2: merry
Task1: tom
hello world 2023-05-30 15:04:05.0003003 +0800 CST m=+11.418630001
......
6、主要類型或接口說明
6.1 Job
任務(wù)抽象(業(yè)務(wù)隔離):任務(wù)抽象成一個(gè) Job 接口,業(yè)務(wù)邏輯類只需實(shí)現(xiàn)該接口。
每一個(gè)實(shí)體包含一個(gè)需要運(yùn)行的 Job,這是一個(gè)接口,只有一個(gè)方法:Run。
// Job is an interface for submitted cron jobs.
type Job interface {
Run()
}由于 Entity 中需要 Job 類型,因此,我們希望定期運(yùn)行的任務(wù),就需要實(shí)現(xiàn) Job 接口。同時(shí),由于 Job 接口只有一個(gè)無參數(shù)無返回值的方法,為了使用方便,作者提供了一個(gè)類型
// 它通過簡(jiǎn)單的實(shí)現(xiàn)Run()方法來實(shí)現(xiàn)Job接口
type FuncJob func()
// 這樣,任何無參數(shù)無返回值的函數(shù),通過強(qiáng)制類型轉(zhuǎn)換為FuncJob,就可以當(dāng)作Job來使用了,AddFunc方法就是這么做的
func (f FuncJob) Run() { f() }6.2 Schedule
計(jì)劃接口:通過當(dāng)前時(shí)間計(jì)算任務(wù)的下次執(zhí)行執(zhí)行時(shí)間,具體實(shí)現(xiàn)類可以根據(jù)實(shí)際需求實(shí)現(xiàn)。
每個(gè)實(shí)體包含一個(gè)調(diào)度器(Schedule)負(fù)責(zé)調(diào)度 Job 的執(zhí)行。它也是一個(gè)接口,Schedule 的具體實(shí)現(xiàn)通過解析 Cron
表達(dá)式得到。庫(kù)中提供了 Schedule 的兩個(gè)具體實(shí)現(xiàn),分別是 SpecSchedule 和 ConstantDelaySchedule。
// Schedule describes a job's duty cycle.
type Schedule interface {
// Next returns the next activation time, later than the given time.
// Next is invoked initially, and then each time the job is run.
// 返回同一Entity中的Job下一次執(zhí)行的時(shí)間
Next(time.Time) time.Time
}6.2.1 SpecSchedule
從開始介紹的 Cron 表達(dá)式可以容易得知各個(gè)字段的意思,同時(shí),對(duì)各種表達(dá)式的解析也會(huì)最終得到一個(gè)
SpecSchedule 的實(shí)例。庫(kù)中的 Parse 返回的其實(shí)就是 SpecSchedule 的實(shí)例(當(dāng)然也就實(shí)現(xiàn)了 Schedule 接口)。
// SpecSchedule specifies a duty cycle (to the second granularity), based on a
// traditional crontab specification. It is computed initially and stored as bit sets.
type SpecSchedule struct {
?? ?Second, Minute, Hour, Dom, Month, Dow uint64
?? ?// Override location for this schedule.
?? ?Location *time.Location
}該類的 Next 方法實(shí)現(xiàn)比較多,這里就不介紹了。
6.2.2 ConstantDelaySchedule
// ConstantDelaySchedule represents a simple recurring duty cycle, e.g. "Every 5 minutes".
// It does not support jobs more frequent than once a second.
type ConstantDelaySchedule struct {
// 循環(huán)的時(shí)間間隔
Delay time.Duration
}這是一個(gè)簡(jiǎn)單的循環(huán)調(diào)度器,如:每 5 分鐘。注意,最小單位是秒,不能比秒還小,比如毫秒。
實(shí)現(xiàn):
// Next returns the next time this should be run.
// This rounds so that the next activation time will be on the second.
func (schedule ConstantDelaySchedule) Next(t time.Time) time.Time {
return t.Add(schedule.Delay - time.Duration(t.Nanosecond())*time.Nanosecond)
}通過 Every 函數(shù)可以獲取該類型的實(shí)例,如下得到的是一個(gè)每 5 秒執(zhí)行一次的調(diào)度器。
package main
import (
?? ?"fmt"
?? ?"github.com/robfig/cron/v3"
)
func main() {
?? ?constDelaySchedule1 := cron.Every(5e9)
?? ?// 5s
?? ?fmt.Println(constDelaySchedule1.Delay)
?? ?constDelaySchedule2 := cron.Every(5e6)
?? ?// 1s
?? ?fmt.Println(constDelaySchedule2.Delay)
}Every 的實(shí)現(xiàn):
// Every returns a crontab Schedule that activates once every duration.
// Delays of less than a second are not supported (will round up to 1 second).
// Any fields less than a Second are truncated.
func Every(duration time.Duration) ConstantDelaySchedule {
if duration < time.Second {
duration = time.Second
}
return ConstantDelaySchedule{
Delay: duration - time.Duration(duration.Nanoseconds())%time.Second,
}
}6.3 Entry
定時(shí)任務(wù)對(duì)象:保存執(zhí)行的任務(wù)Job、計(jì)算執(zhí)行時(shí)間。
// Entry consists of a schedule and the func to execute on that schedule.
type Entry struct {
?? ?// ID is the cron-assigned ID of this entry, which may be used to look up a
?? ?// snapshot or remove it.
?? ?ID EntryID
?? ?// Schedule on which this job should be run.
? ?? ?// 負(fù)責(zé)調(diào)度當(dāng)前Entity中的Job執(zhí)行
?? ?Schedule Schedule
?? ?// Next time the job will run, or the zero time if Cron has not been
?? ?// started or this entry's schedule is unsatisfiable
? ?? ?// Job下一次執(zhí)行的時(shí)間
?? ?Next time.Time
?? ?// Prev is the last time this job was run, or the zero time if never.
? ?? ?// 上一次執(zhí)行時(shí)間
?? ?Prev time.Time
?? ?// WrappedJob is the thing to run when the Schedule is activated.
?? ?WrappedJob Job
?? ?// Job is the thing that was submitted to cron.
?? ?// It is kept around so that user code that needs to get at the job later,
?? ?// e.g. via Entries() can do so.
? ?? ?// 要執(zhí)行的Job
?? ?Job Job
}6.4 Cron
任務(wù)調(diào)度管理:保存定時(shí)任務(wù)對(duì)象(Entry),調(diào)度任務(wù)執(zhí)行,提供新增、刪除接口(涉及關(guān)聯(lián)資源競(jìng)爭(zhēng))和暫停。
注意:
- Cron 結(jié)構(gòu)沒有導(dǎo)出任何成員。
- 有一個(gè)成員stop,類型是struct{},即空結(jié)構(gòu)體。
// Cron keeps track of any number of entries, invoking the associated func as
// specified by the schedule. It may be started, stopped, and the entries may
// be inspected while running.
type Cron struct {
entries []*Entry
chain Chain
stop chan struct{} // 控制Cron實(shí)例暫停
add chan *Entry // 當(dāng)Cron已經(jīng)運(yùn)行了,增加新的Entity是通過add這個(gè)channel實(shí)現(xiàn)的
remove chan EntryID
snapshot chan chan []Entry // 獲取當(dāng)前所有entity的快照
running bool // 當(dāng)已經(jīng)運(yùn)行時(shí)為true,否則為false
logger Logger
runningMu sync.Mutex
location *time.Location
parser Parser
nextID EntryID
jobWaiter sync.WaitGroup
}// Remove an entry from being run in the future.
func (c *Cron) Remove(id EntryID) {
?? ?c.runningMu.Lock()
?? ?defer c.runningMu.Unlock()
?? ?if c.running {
?? ??? ?c.remove <- id
?? ?} else {
?? ??? ?c.removeEntry(id)
?? ?}
}
// Schedule adds a Job to the Cron to be run on the given schedule.
// The job is wrapped with the configured Chain.
func (c *Cron) Schedule(schedule Schedule, cmd Job) EntryID {
?? ?c.runningMu.Lock()
?? ?defer c.runningMu.Unlock()
?? ?c.nextID++
?? ?entry := &Entry{
?? ??? ?ID: ? ? ? ? c.nextID,
?? ??? ?Schedule: ? schedule,
?? ??? ?WrappedJob: c.chain.Then(cmd),
?? ??? ?Job: ? ? ? ?cmd,
?? ?}
?? ?if !c.running {
?? ??? ?c.entries = append(c.entries, entry)
?? ?} else {
?? ??? ?c.add <- entry
?? ?}
?? ?return entry.ID
}7、實(shí)例化主要方法說明
7.1 實(shí)例化
啟動(dòng)時(shí)會(huì)開啟唯一協(xié)程執(zhí)行run方法,計(jì)算任務(wù)執(zhí)行時(shí)間,執(zhí)行,任務(wù)管理等:
// New returns a new Cron job runner, modified by the given options.
//
// Available Settings
//
// ? Time Zone
// ? ? Description: The time zone in which schedules are interpreted
// ? ? Default: ? ? time.Local
//
// ? Parser
// ? ? Description: Parser converts cron spec strings into cron.Schedules.
// ? ? Default: ? ? Accepts this spec: https://en.wikipedia.org/wiki/Cron
//
// ? Chain
// ? ? Description: Wrap submitted jobs to customize behavior.
// ? ? Default: ? ? A chain that recovers panics and logs them to stderr.
//
// See "cron.With*" to modify the default behavior.
// 實(shí)例化時(shí),成員使用的基本是默認(rèn)值
func New(opts ...Option) *Cron {
?? ?c := &Cron{
?? ??? ?entries: ? nil,
?? ??? ?chain: ? ? NewChain(),
?? ??? ?add: ? ? ? make(chan *Entry),
?? ??? ?stop: ? ? ?make(chan struct{}),
?? ??? ?snapshot: ?make(chan chan []Entry),
?? ??? ?remove: ? ?make(chan EntryID),
?? ??? ?running: ? false,
?? ??? ?runningMu: sync.Mutex{},
?? ??? ?logger: ? ?DefaultLogger,
?? ??? ?location: ?time.Local,
?? ??? ?parser: ? ?standardParser,
?? ?}
?? ?for _, opt := range opts {
?? ??? ?opt(c)
?? ?}
?? ?return c
}
// Start the cron scheduler in its own goroutine, or no-op if already started.
func (c *Cron) Start() {
?? ?c.runningMu.Lock()
?? ?defer c.runningMu.Unlock()
?? ?if c.running {
?? ??? ?return
?? ?}
?? ?c.running = true
?? ?go c.run()
}7.2 主要方法
核心調(diào)度:計(jì)算下次執(zhí)行時(shí)間 -> 排序 -> 取最早執(zhí)行數(shù)據(jù) -> timer 等待,因?yàn)橹挥幸粋€(gè)協(xié)程在執(zhí)行這個(gè)run的調(diào)
度,所以不存在資源競(jìng)爭(zhēng),不需要加鎖,另外考慮到執(zhí)行任務(wù)可能涉及阻塞,例如:IO操作,所以一般startJob方
法會(huì)開啟協(xié)程執(zhí)行。
// Run the cron scheduler, or no-op if already running.
func (c *Cron) Run() {
?? ?c.runningMu.Lock()
?? ?if c.running {
?? ??? ?c.runningMu.Unlock()
?? ??? ?return
?? ?}
?? ?c.running = true
?? ?c.runningMu.Unlock()
?? ?c.run()
}
// run the scheduler.. this is private just due to the need to synchronize
// access to the 'running' state variable.
func (c *Cron) run() {
?? ?c.logger.Info("start")
?? ?// Figure out the next activation times for each entry.
?? ?now := c.now()
?? ?for _, entry := range c.entries {
?? ??? ?entry.Next = entry.Schedule.Next(now)
?? ??? ?c.logger.Info("schedule", "now", now, "entry", entry.ID, "next", entry.Next)
?? ?}
?? ?for {
?? ??? ?// Determine the next entry to run.
?? ??? ?sort.Sort(byTime(c.entries))
?? ??? ?var timer *time.Timer
?? ??? ?if len(c.entries) == 0 || c.entries[0].Next.IsZero() {
?? ??? ??? ?// If there are no entries yet, just sleep - it still handles new entries
?? ??? ??? ?// and stop requests.
?? ??? ??? ?timer = time.NewTimer(100000 * time.Hour)
?? ??? ?} else {
?? ??? ??? ?timer = time.NewTimer(c.entries[0].Next.Sub(now))
?? ??? ?}
?? ??? ?for {
?? ??? ??? ?select {
?? ??? ??? ?case now = <-timer.C:
?? ??? ??? ??? ?now = now.In(c.location)
?? ??? ??? ??? ?c.logger.Info("wake", "now", now)
?? ??? ??? ??? ?// Run every entry whose next time was less than now
?? ??? ??? ??? ?for _, e := range c.entries {
?? ??? ??? ??? ??? ?if e.Next.After(now) || e.Next.IsZero() {
?? ??? ??? ??? ??? ??? ?break
?? ??? ??? ??? ??? ?}
?? ??? ??? ??? ??? ?c.startJob(e.WrappedJob)
?? ??? ??? ??? ??? ?e.Prev = e.Next
?? ??? ??? ??? ??? ?e.Next = e.Schedule.Next(now)
?? ??? ??? ??? ??? ?c.logger.Info("run", "now", now, "entry", e.ID, "next", e.Next)
?? ??? ??? ??? ?}
?? ??? ??? ?case newEntry := <-c.add:
?? ??? ??? ??? ?timer.Stop()
?? ??? ??? ??? ?now = c.now()
?? ??? ??? ??? ?newEntry.Next = newEntry.Schedule.Next(now)
?? ??? ??? ??? ?c.entries = append(c.entries, newEntry)
?? ??? ??? ??? ?c.logger.Info("added", "now", now, "entry", newEntry.ID, "next", newEntry.Next)
?? ??? ??? ?case replyChan := <-c.snapshot:
?? ??? ??? ??? ?replyChan <- c.entrySnapshot()
?? ??? ??? ??? ?continue
?? ??? ??? ?case <-c.stop:
?? ??? ??? ??? ?timer.Stop()
?? ??? ??? ??? ?c.logger.Info("stop")
?? ??? ??? ??? ?return
?? ??? ??? ?case id := <-c.remove:
?? ??? ??? ??? ?timer.Stop()
?? ??? ??? ??? ?now = c.now()
?? ??? ??? ??? ?c.removeEntry(id)
?? ??? ??? ??? ?c.logger.Info("removed", "entry", id)
?? ??? ??? ?}
?? ??? ??? ?break
?? ??? ?}
?? ?}
}
// startJob runs the given job in a new goroutine.
func (c *Cron) startJob(j Job) {
?? ?c.jobWaiter.Add(1)
?? ?go func() {
?? ??? ?defer c.jobWaiter.Done()
?? ??? ?j.Run()
?? ?}()
}7.3 其它成員方法
// EntryID標(biāo)識(shí)Cron實(shí)例中的entry
type EntryID int
// 將job加入Cron中
// 如上所述,該方法只是簡(jiǎn)單的通過FuncJob類型強(qiáng)制轉(zhuǎn)換cmd,然后調(diào)用AddJob方法
func (c *Cron) AddFunc(spec string, cmd func()) (EntryID, error)?
// 將job加入Cron中
// 通過Parse函數(shù)解析cron表達(dá)式spec的到調(diào)度器實(shí)例(Schedule),之后調(diào)用c.Schedule方法
func (c *Cron) AddJob(spec string, cmd Job) (EntryID, error)
// 獲取當(dāng)前Cron總所有Entities的快照
func (c *Cron) Entries() []*Entry
// Location獲取時(shí)區(qū)位置
func (c *Cron) Location() *time.Location?
// Entry返回給定項(xiàng)的快照,如果找不到則返回nil
func (c *Cron) Entry(id EntryID) Entry
// 刪除將來運(yùn)行的條目
func (c *Cron) Remove(id EntryID)?
// 通過兩個(gè)參數(shù)實(shí)例化一個(gè)Entity,然后加入當(dāng)前Cron中
// 注意: 如果當(dāng)前Cron未運(yùn)行,則直接將該entity加入Cron中
// 否則,通過add這個(gè)成員channel將entity加入正在運(yùn)行的Cron中
func (c *Cron) Schedule(schedule Schedule, cmd Job) EntryID
// 新啟動(dòng)一個(gè)goroutine運(yùn)行當(dāng)前Cron
func (c *Cron) Start()
// 通過給stop成員發(fā)送一個(gè)struct{}{}來停止當(dāng)前Cron,同時(shí)將running置為false
// 從這里知道,stop只是通知Cron停止,因此往channel發(fā)一個(gè)值即可,而不關(guān)心值是多少
// 所以,成員stop定義為空struct
func (c *Cron) Stop()
// 運(yùn)行cron調(diào)度程序,如果已經(jīng)在運(yùn)行,則不運(yùn)行op
func (c *Cron) Run()8、其它定時(shí)任務(wù)的實(shí)現(xiàn)
8.1 最簡(jiǎn)單的定時(shí)任務(wù)
使用協(xié)程和 Sleep方式:
package main
import (
?? ?"fmt"
?? ?"time"
)
func main(){
?? ?go func() {
?? ??? ?for true {
?? ??? ??? ?fmt.Println("Hello World!",time.Now())
?? ??? ??? ?time.Sleep(1 * time.Second)
?? ??? ?}
?? ?}()
?? ?select {
?? ?}
}# 輸出
Hello World! 2023-05-30 22:26:12.454977 +0800 CST m=+7.021813601
Hello World! 2023-05-30 22:26:13.4553086 +0800 CST m=+8.022145201
Hello World! 2023-05-30 22:26:14.4555344 +0800 CST m=+9.022371001
Hello World! 2023-05-30 22:26:15.4566625 +0800 CST m=+10.023499101
Hello World! 2023-05-30 22:26:16.4570511 +0800 CST m=+11.023887701
Hello World! 2023-05-30 22:26:17.4579146 +0800 CST m=+12.024751201
Hello World! 2023-05-30 22:26:18.4589781 +0800 CST m=+13.025814701
......
8.2 Timer實(shí)現(xiàn)定時(shí)任務(wù)
除了使用 cron 庫(kù)可以實(shí)現(xiàn)定時(shí)任務(wù),使用 time 庫(kù)也可以實(shí)現(xiàn)定時(shí)任務(wù)。
在Go語言中,可以使用 time 包提供的 Timer 和 Ticker 類型設(shè)置定時(shí)任務(wù)。Timer 用于在未來的某個(gè)時(shí)間點(diǎn)執(zhí)行
一次任務(wù),而 Ticker 則用于每隔一定時(shí)間執(zhí)行一次任務(wù)。
8.2.1 Timer啟動(dòng)定時(shí)器
Timer 實(shí)現(xiàn)定時(shí)器,延遲執(zhí)行,這個(gè)定時(shí)器只會(huì)觸發(fā)一次。
下面是一個(gè)使用 Timer 設(shè)置定時(shí)任務(wù)的例子:
package main
import (
?? ?"fmt"
?? ?"time"
)
func main() {
?? ?// 創(chuàng)建一個(gè)Timer實(shí)例,設(shè)置2秒后執(zhí)行任務(wù)
?? ?t := time.NewTimer(2 * time.Second)
?? ?// 記得釋放Timer資源
?? ?defer t.Stop()
?? ?// 等待Timer到期
?? ?<-t.C
?? ?// 執(zhí)行任務(wù)
?? ?fmt.Println("Task executed at", time.Now())
}# 輸出
Task executed at 2023-05-30 21:58:32.5390386 +0800 CST m=+2.002460501
8.2.2 Timer停止定時(shí)器
使用 time.Stop() 停止定時(shí)器,通過向通道發(fā)送一個(gè)信號(hào),通知定時(shí)器是否關(guān)閉。
package main
import (
?? ?"fmt"
?? ?"time"
)
func main() {
?? ?done := make(chan bool)
?? ?ticker := time.NewTimer(1 * time.Second)
?? ?go func() {
?? ??? ?for {
?? ??? ??? ?select {
?? ??? ??? ?case <-done:
?? ??? ??? ??? ?ticker.Stop()
?? ??? ??? ??? ?return
?? ??? ??? ?case <-ticker.C:
?? ??? ??? ??? ?fmt.Println("Hello World!")
?? ??? ??? ?}
?? ??? ?}
?? ?}()
?? ?time.Sleep(10 *time.Second)
?? ?done <- true
}# 輸出
Hello World!
8.2.3 Timer重置定時(shí)器
package main
import (
?? ?"fmt"
?? ?"time"
)
func main() {
?? ?fmt.Println("hello world", time.Now())
?? ?// 創(chuàng)建一個(gè)定時(shí)器
?? ?// 設(shè)置7秒后執(zhí)行一次
?? ?myT := time.NewTimer(7 * time.Second)
?? ?// 重置定時(shí)器為1s后執(zhí)行
?? ?myT.Reset(1 * time.Second)
?? ?<-myT.C
?? ?fmt.Println("hello world", time.Now())
}# 輸出
hello world 2023-05-30 22:13:03.4342176 +0800 CST m=+0.002177501
hello world 2023-05-30 22:13:04.4454858 +0800 CST m=+1.013445701
8.2.4 Ticker啟動(dòng)定時(shí)器
Ticker 也是定時(shí)器,它是一個(gè)周期性的定時(shí)器。
package main
import (
?? ?"fmt"
?? ?"time"
)
func main() {
?? ?// 創(chuàng)建一個(gè)Ticker實(shí)例,每隔1秒執(zhí)行一次任務(wù)
?? ?ticker := time.NewTicker(1 * time.Second)
?? ?// 記得釋放Ticker資源
?? ?defer ticker.Stop()
?? ?// 循環(huán)處理任務(wù)
?? ?for {
?? ??? ?// 等待Ticker的下一次觸發(fā)
?? ??? ?<-ticker.C
?? ??? ?// 執(zhí)行任務(wù)
?? ??? ?fmt.Println("Task executed at", time.Now())
?? ?}
}# 輸出
Task executed at 2023-05-30 22:17:57.2393424 +0800 CST m=+1.007115201
Task executed at 2023-05-30 22:17:58.2428548 +0800 CST m=+2.010627601
Task executed at 2023-05-30 22:17:59.2431966 +0800 CST m=+3.010969401
Task executed at 2023-05-30 22:18:00.2455851 +0800 CST m=+4.013357901
Task executed at 2023-05-30 22:18:01.2438882 +0800 CST m=+5.011661001
......
package main
import (
?? ?"fmt"
?? ?"time"
)
func main() {
?? ?// 創(chuàng)建一個(gè)定時(shí)器,每隔1秒觸發(fā)一次
?? ?ticker := time.NewTicker(1 * time.Second)
?? ?// 在函數(shù)退出時(shí)停止定時(shí)器
?? ?defer ticker.Stop()
?? ?// timer實(shí)現(xiàn)定時(shí)器(延遲執(zhí)行),這個(gè)定時(shí)器只會(huì)觸發(fā)一次,所以想要執(zhí)行定時(shí)任務(wù)需要放在for循環(huán)中
?? ?for {
?? ??? ?select {
?? ??? ?// 定時(shí)器觸發(fā)時(shí)執(zhí)行的任務(wù)
?? ??? ?case <-ticker.C:
?? ??? ??? ?fmt.Println("hello world", time.Now())
?? ??? ?}
?? ?}
}# 輸出
hello world 2023-05-30 21:15:42.32353 +0800 CST m=+42.004562601
hello world 2023-05-30 21:15:43.3274285 +0800 CST m=+43.008461101
hello world 2023-05-30 21:15:44.3226011 +0800 CST m=+44.003633701
hello world 2023-05-30 21:15:45.3233505 +0800 CST m=+45.004383101
hello world 2023-05-30 21:15:46.3310151 +0800 CST m=+46.012047701
hello world 2023-05-30 21:15:47.3234301 +0800 CST m=+47.004462701
hello world 2023-05-30 21:15:48.3243248 +0800 CST m=+48.005357401
......
8.2.5 Ticker停止定時(shí)器
package main
import (
?? ?"fmt"
?? ?"time"
)
func main() {
?? ?ticker := time.NewTicker(1 * time.Second)
?? ?go func() {
?? ??? ?for range ticker.C {
?? ??? ??? ?fmt.Println("Hello World!")
?? ??? ?}
?? ?}()
?? ?time.Sleep(10 * time.Second)
?? ?ticker.Stop()
}# 輸出
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
package main
import (
?? ?"fmt"
?? ?"time"
)
func main() {
?? ?done := make(chan bool)
?? ?ticker := time.NewTicker(1 * time.Second)
?? ?go func() {
?? ??? ?for {
?? ??? ??? ?select {
?? ??? ??? ?case <-done:
?? ??? ??? ??? ?ticker.Stop()
?? ??? ??? ??? ?return
?? ??? ??? ?case <-ticker.C:
?? ??? ??? ??? ?fmt.Println("Hello World!")
?? ??? ??? ?}
?? ??? ?}
?? ?}()
?? ?time.Sleep(10 *time.Second)
?? ?done <- true
}# 輸出
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
8.3 gocron庫(kù)
8.3.1 安裝
go get -u github.com/go-co-op/gocron
8.3.2 使用
s := gocron.NewScheduler(time.UTC)
s.Every(5).Seconds().Do(func(){ ... })
s.Every("5m").Do(func(){ ... })
s.Every(5).Days().Do(fu
s.Every(1).Month(1, 2, 3).Do(func(){ ... })
s.Every(1).Day().At("10:30").Do(func(){ ... })
s.Every(1).Day().At("10:30;08:00").Do(func(){ ... })
s.Every(1).Day().At("10:30").At("08:00").Do(func(){ ... })
s.Every(1).MonthLastDay().Do(func(){ ... })
s.Every(2).MonthLastDay().Do(func(){ ... })
s.Cron("*/1 * * * *").Do(task)
s.StartAsync()
s.StartBlocking()8.3.3 例子
package main
import (
?? ?"fmt"
?? ?"time"
?? ?"github.com/go-co-op/gocron"
)
func cron1() {
?? ?fmt.Println("cron1",time.Now())
}
func cron2() {
?? ?fmt.Println("cron2",time.Now())
}
func main() {
?? ?timezone, _ := time.LoadLocation("Asia/Shanghai")
?? ?s := gocron.NewScheduler(timezone)
?? ?// 每秒執(zhí)行一次
?? ?s.Every(1).Seconds().Do(func() {
?? ??? ?go cron1()
?? ?})
?? ?// 每秒執(zhí)行一次
?? ?s.Every(1).Second().Do(func() {
?? ??? ?go cron2()
?? ?})
?? ?s.StartBlocking()
}# 輸出
cron2 2023-05-30 22:28:09.1476998 +0800 CST m=+0.002582801
cron1 2023-05-30 22:28:09.1476998 +0800 CST m=+0.002582801
cron2 2023-05-30 22:28:10.1478397 +0800 CST m=+1.002722701
cron1 2023-05-30 22:28:10.1478397 +0800 CST m=+1.002722701
cron1 2023-05-30 22:28:11.1487562 +0800 CST m=+2.003639201
cron2 2023-05-30 22:28:11.1487562 +0800 CST m=+2.003639201
cron2 2023-05-30 22:28:12.1479533 +0800 CST m=+3.002836301
cron1 2023-05-30 22:28:12.1479533 +0800 CST m=+3.002836301
......
到此這篇關(guān)于Go語言定時(shí)任務(wù)的實(shí)現(xiàn)示例的文章就介紹到這了,更多相關(guān)Go語言定時(shí)任務(wù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang內(nèi)存對(duì)齊的項(xiàng)目實(shí)踐
本文主要介紹了golang內(nèi)存對(duì)齊的項(xiàng)目實(shí)踐,內(nèi)存對(duì)齊不僅有助于提高內(nèi)存訪問效率,還確保了與硬件接口的兼容性,是Go語言編程中不可忽視的重要優(yōu)化手段,下面就來介紹一下2025-02-02
自己動(dòng)手用Golang實(shí)現(xiàn)約瑟夫環(huán)算法的示例
這篇文章主要介紹了自己動(dòng)手用Golang實(shí)現(xiàn)約瑟夫環(huán)算法的示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12
一文搞懂Go語言標(biāo)準(zhǔn)庫(kù)strconv
strconv包實(shí)現(xiàn)了基本數(shù)據(jù)類型和其字符串表示的相互轉(zhuǎn)換,本文主要介紹Go語言標(biāo)準(zhǔn)庫(kù)strconv,想要學(xué)習(xí)strconv標(biāo)準(zhǔn)庫(kù)的可以了解一下2023-04-04
GO項(xiàng)目部署Linux服務(wù)器的實(shí)現(xiàn)示例
本文主要介紹了GO項(xiàng)目部署Linux服務(wù)器的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-06-06
詳解Go語言中init的使用與常見應(yīng)用場(chǎng)景
Go?中有一個(gè)特別的?init()?函數(shù),它主要用于包的初始化,這篇文章將以此為主題介紹?Go?中?init()?函數(shù)的使用和常見使用場(chǎng)景,希望對(duì)大家有所幫助2024-02-02

