Go語(yǔ)言定時(shí)任務(wù)的實(shí)現(xiàn)示例
robfig/cron 是Go語(yǔ)言實(shí)現(xiàn)的開(kāi)源定時(shí)任務(wù)調(diào)度框架,核心代碼是巧妙的使用chan + select + for實(shí)現(xiàn)了一個(gè)輕量
級(jí)調(diào)度協(xié)程,不但語(yǔ)法簡(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á)式格式也是從秒開(kāi)始 ?? ?// 默認(rèn)從分開(kāi)始進(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 語(yǔ)言中也可以用到。
Linux 也是可以用 crontab -e 命令來(lái)配置定時(shí)任務(wù)。
Go 語(yǔ)言和 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 | * / , - ? |
特殊字符說(shuō)明:
? 只能在 day 和 week 中使用,標(biāo)識(shí)未說(shuō)明的值,用以解決 day 和 week 的沖突,比如 * * * 10 * ? 表示每
月10號(hào)觸發(fā),而換成 * 則表示不管星期幾都可觸發(fā),與前者發(fā)生沖突。
3.1 Cron表達(dá)式說(shuō)明
- 月(Month)和星期(Day of week)字段的值不區(qū)分大小寫(xiě),如:SUN、Sun 和 sun 是一樣的。
- 星期(Day of week)字段如果沒(méi)提供,相當(dāng)于是 *
3.2 Cron表達(dá)式示例說(shuō)明
如果我們使用 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特定字符說(shuō)明
符號(hào) | 說(shuō)明 |
---|---|
(*) | 表示 cron 表達(dá)式能匹配該字段的所有值。如在第5個(gè)字段使用星號(hào)(month),表示每個(gè)月 |
(/) | 表示增長(zhǎng)間隔,如第1個(gè)字段(minutes) 值是 3-59/15,表示每小時(shí)的第3分鐘開(kāi)始執(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á)式來(lái)代替上表的表達(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í)開(kāi)始。這是通過(guò)如下格式化cron規(guī)范來(lái)支持的:
@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、主要類(lèi)型或接口說(shuō)明
6.1 Job
任務(wù)抽象(業(yè)務(wù)隔離):任務(wù)抽象成一個(gè) Job 接口,業(yè)務(wù)邏輯類(lèi)只需實(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 類(lèi)型,因此,我們希望定期運(yùn)行的任務(wù),就需要實(shí)現(xiàn) Job 接口。同時(shí),由于 Job 接口只有一個(gè)無(wú)參數(shù)無(wú)返回值的方法,為了使用方便,作者提供了一個(gè)類(lèi)型
// 它通過(guò)簡(jiǎn)單的實(shí)現(xiàn)Run()方法來(lái)實(shí)現(xiàn)Job接口 type FuncJob func() // 這樣,任何無(wú)參數(shù)無(wú)返回值的函數(shù),通過(guò)強(qiáng)制類(lèi)型轉(zhuǎn)換為FuncJob,就可以當(dāng)作Job來(lái)使用了,AddFunc方法就是這么做的 func (f FuncJob) Run() { f() }
6.2 Schedule
計(jì)劃接口:通過(guò)當(dāng)前時(shí)間計(jì)算任務(wù)的下次執(zhí)行執(zhí)行時(shí)間,具體實(shí)現(xiàn)類(lèi)可以根據(jù)實(shí)際需求實(shí)現(xiàn)。
每個(gè)實(shí)體包含一個(gè)調(diào)度器(Schedule)負(fù)責(zé)調(diào)度 Job 的執(zhí)行。它也是一個(gè)接口,Schedule 的具體實(shí)現(xiàn)通過(guò)解析 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
從開(kāi)始介紹的 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 }
該類(lèi)的 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) }
通過(guò) Every 函數(shù)可以獲取該類(lèi)型的實(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)沒(méi)有導(dǎo)出任何成員。
- 有一個(gè)成員stop,類(lèi)型是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是通過(guò)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í)例化主要方法說(shuō)明
7.1 實(shí)例化
啟動(dòng)時(shí)會(huì)開(kāi)啟唯一協(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ì)開(kāi)啟協(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)單的通過(guò)FuncJob類(lèi)型強(qiáng)制轉(zhuǎn)換cmd,然后調(diào)用AddJob方法 func (c *Cron) AddFunc(spec string, cmd func()) (EntryID, error)? // 將job加入Cron中 // 通過(guò)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 // 刪除將來(lái)運(yùn)行的條目 func (c *Cron) Remove(id EntryID)? // 通過(guò)兩個(gè)參數(shù)實(shí)例化一個(gè)Entity,然后加入當(dāng)前Cron中 // 注意: 如果當(dāng)前Cron未運(yùn)行,則直接將該entity加入Cron中 // 否則,通過(guò)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() // 通過(guò)給stop成員發(fā)送一個(gè)struct{}{}來(lái)停止當(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語(yǔ)言中,可以使用 time 包提供的 Timer 和 Ticker 類(lèi)型設(shè)置定時(shí)任務(wù)。Timer 用于在未來(lái)的某個(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í)器,通過(guò)向通道發(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語(yǔ)言定時(shí)任務(wù)的實(shí)現(xiàn)示例的文章就介紹到這了,更多相關(guān)Go語(yǔ)言定時(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)存訪問(wèn)效率,還確保了與硬件接口的兼容性,是Go語(yǔ)言編程中不可忽視的重要優(yōu)化手段,下面就來(lái)介紹一下2025-02-02自己動(dòng)手用Golang實(shí)現(xiàn)約瑟夫環(huán)算法的示例
這篇文章主要介紹了自己動(dòng)手用Golang實(shí)現(xiàn)約瑟夫環(huán)算法的示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12一文搞懂Go語(yǔ)言標(biāo)準(zhǔn)庫(kù)strconv
strconv包實(shí)現(xiàn)了基本數(shù)據(jù)類(lèi)型和其字符串表示的相互轉(zhuǎn)換,本文主要介紹Go語(yǔ)言標(biāo)準(zhǔn)庫(kù)strconv,想要學(xué)習(xí)strconv標(biāo)準(zhǔn)庫(kù)的可以了解一下2023-04-04GO項(xiàng)目部署Linux服務(wù)器的實(shí)現(xiàn)示例
本文主要介紹了GO項(xiàng)目部署Linux服務(wù)器的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-06-06詳解Go語(yǔ)言中init的使用與常見(jiàn)應(yīng)用場(chǎng)景
Go?中有一個(gè)特別的?init()?函數(shù),它主要用于包的初始化,這篇文章將以此為主題介紹?Go?中?init()?函數(shù)的使用和常見(jiàn)使用場(chǎng)景,希望對(duì)大家有所幫助2024-02-02