Go語言定時任務(wù)的實現(xiàn)示例
robfig/cron 是Go語言實現(xiàn)的開源定時任務(wù)調(diào)度框架,核心代碼是巧妙的使用chan + select + for實現(xiàn)了一個輕量
級調(diào)度協(xié)程,不但語法簡潔,而且具有很好的性能。
Cron是Go中用于設(shè)置定時任務(wù)的一個庫,需要注意的是,Cron庫分兩個大版本,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倉庫: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、定時任務(wù)簡單案例
package main import ( ?? ?"fmt" ?? ?"github.com/robfig/cron/v3" ?? ?"time" ) func main() { ?? ?// 新建一個定時任務(wù)對象,根據(jù)cron表達式進行時間調(diào)度,cron可以精確到秒,大部分表達式格式也是從秒開始 ?? ?// 默認從分開始進行時間調(diào)度 ?? ?// cronTab := cron.New() ?? ?// 精確到秒 ?? ?cronTab := cron.New(cron.WithSeconds()) ?? ?// 定義定時器調(diào)用的任務(wù)函數(shù) ?? ?task := func() { ?? ??? ?fmt.Println("hello world", time.Now()) ?? ?} ?? ?// 定時任務(wù),cron表達式,每五秒一次 ?? ?spec := "*/5 * * * * ?" ?? ?// 添加定時任務(wù) ?? ?cronTab.AddFunc(spec, task) ?? ?// 啟動定時器 ?? ?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表達式
cron 表達式是一個好東西,這個東西不僅 Java 的 quartZ 能用到,Go 語言中也可以用到。
Linux 也是可以用 crontab -e 命令來配置定時任務(wù)。
Go 語言和 Java 中都是可以精確到秒的,但是 Linux 中不行。
cron表達式代表一個時間的集合,使用6個空格分隔的字段表示:
字段名 | 是否必須 | 允許的值 | 允許的特定字符 |
---|---|---|---|
秒(Seconds) | 是 | 0-59 | * / , - |
分(Minute) | 是 | 0-59 | * / , - |
時(Hours) | 是 | 0-23 | * / , - |
日(Day of month) | 是 | 1-31 | * / , - ? |
月(Month) | 是 | 1-12 或 JAN-DEC | * / , - |
星期(Day of week) | 否 | 0-6 或 SUM-SAT | * / , - ? |
特殊字符說明:
? 只能在 day 和 week 中使用,標識未說明的值,用以解決 day 和 week 的沖突,比如 * * * 10 * ? 表示每
月10號觸發(fā),而換成 * 則表示不管星期幾都可觸發(fā),與前者發(fā)生沖突。
3.1 Cron表達式說明
- 月(Month)和星期(Day of week)字段的值不區(qū)分大小寫,如:SUN、Sun 和 sun 是一樣的。
- 星期(Day of week)字段如果沒提供,相當于是 *
3.2 Cron表達式示例說明
如果我們使用 crontab := cron.New(cron.WithSeconds()),我們的定時任務(wù)表達式需要為:
* * * * * *
如果我們使用 cronTab := cron.New(),我們的定時任務(wù)表達式需要為:
* * * * *,不包含秒
這 6 個 * 分別代表什么意思呢?
# 第一個*: second,范圍(0 - 60)
# 第二個*: min,范圍(0 - 59)
# 第三個*: hour,范圍(0 - 23)
# 第四個*: day of month,范圍(1 - 31)
# 第五個*: month,范圍(1 - 12)
# 第六個*: day of week,范圍(0 - 6) (0 to 6 are Sunday to Saturday)
* * * * * *
3.3 cron特定字符說明
符號 | 說明 |
---|---|
(*) | 表示 cron 表達式能匹配該字段的所有值。如在第5個字段使用星號(month),表示每個月 |
(/) | 表示增長間隔,如第1個字段(minutes) 值是 3-59/15,表示每小時的第3分鐘開始執(zhí)行一次,之后每隔 15 分鐘執(zhí)行一次(即 3、18、33、48 這些時間點執(zhí)行),這里也可以表示為:3/15 |
(,) | 用于枚舉值,如第6個字段值是 MON,WED,FRI,表示 星期一、三、五 執(zhí)行 |
(-) | 表示一個范圍,如第3個字段的值為 9-17 表示 9am 到 5pm 直接每個小時(包括9和17) |
(?) | 只用于 日(Day of month) 和 星期(Day of week),表示不指定值,可以用于代替 * |
3.4 常用cron舉例
每隔5秒執(zhí)行一次:*/5 * * * * ?
每隔1分鐘執(zhí)行一次:0 */1 * * * ?
每天23點執(zhí)行一次:0 0 23 * * ?
每天凌晨1點執(zhí)行一次:0 0 1 * * ?
每月1號凌晨1點執(zhí)行一次:0 0 1 1 * ?
每周一和周三晚上22:30: 00 30 22 * * 1,3
在26分、29分、33分執(zhí)行一次:0 26,29,33 * * * ?
每天的0點、13點、18點、21點都執(zhí)行一次:0 0 0,13,18,21 * * ?
每年三月的星期四的下午14:10和14:40: 00 10,40 14 ? 3 4
3.5 預(yù)定義的時間格式
您可以使用幾個預(yù)定義的表達式來代替上表的表達式,使用如下:
輸入 | 描述 | 等式 |
---|---|---|
@yearly (or @annually) | 每年一次,1月1日午夜 | 0 0 0 1 1 * |
@monthly | 每月運行一次,每月第一天午夜 | 0 0 0 1 * * |
@weekly | 每周運行一次,周六/周日之間的午夜 | 0 0 0 * * 0 |
@daily (or @midnight) | 每天午夜運行一次 | 0 0 0 * * * |
@hourly | 每小時運行一次 | 0 0 * * * * |
還可以安排作業(yè)以固定的間隔執(zhí)行,從添加作業(yè)或運行cron時開始。這是通過如下格式化cron規(guī)范來支持的:
@every <duration>
// 例如 c := cron.New() c.AddFunc("@every 1h30m", func() { fmt.Println("Every hour thirty, starting an hour thirty from now") })
4、時區(qū)
默認情況下,所有時間都是基于當前時區(qū)的,也可自定義:在時間字符串前面添加一個CRON_TZ= + 具體時區(qū)
一些常用的時區(qū):
- 東京時區(qū):Asia/Tokyo
- 紐約時區(qū):America/New_York
- 上海時區(qū):Asia/Shanghai
- 香港時區(qū):Asia/Hong_Kong
創(chuàng)建cron對象時增加一個時區(qū)選項cron.WithLocation(location),location為time.LoadLocation(zone)加載的時區(qū)
對象,zone為具體的時區(qū)格式。
package main import ( ?? ?"fmt" ?? ?"github.com/robfig/cron/v3" ?? ?"time" ) func main() { ?? ?//直接配置時區(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ù)里面配置時區(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、自定義定時任務(wù)以及多個定時任務(wù)
自定義定時任務(wù)只需要實現(xiàn)Job接口的Run方法。
package main import ( ?? ?"fmt" ?? ?"github.com/robfig/cron/v3" ?? ?"time" ) type Task1 struct { ?? ?Name string } // 自定義定時任務(wù)只需要實現(xiàn)Job接口的Run方法 func (t *Task1) Run() { ?? ?fmt.Println("Task1: ", t.Name) } type Task2 struct { ?? ?Name string } // 自定義定時任務(wù)只需要實現(xiàn)Job接口的Run方法 func (t *Task2) Run() { ?? ?fmt.Println("Task2: ", t.Name) } func main() { ?? ?cronTab := cron.New(cron.WithSeconds()) ?? ?// 定義定時器調(diào)用的任務(wù)函數(shù) ?? ?// 定時任務(wù) ?? ?// cron表達式,每五秒一次 ?? ?spec := "*/5 * * * * ?" ?? ?//定義定時器調(diào)用的任務(wù)函數(shù) ?? ?task := func() { ?? ??? ?fmt.Println("hello world", time.Now()) ?? ?} ?? ?// 添加多個定時器 ?? ?cronTab.AddFunc(spec, task) ?? ?cronTab.AddJob(spec, &Task1{Name: "tom"}) ?? ?cronTab.AddJob(spec, &Task2{Name: "merry"}) ?? ?// 啟動定時器 ?? ?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ù)抽象成一個 Job 接口,業(yè)務(wù)邏輯類只需實現(xiàn)該接口。
每一個實體包含一個需要運行的 Job,這是一個接口,只有一個方法:Run。
// Job is an interface for submitted cron jobs. type Job interface { Run() }
由于 Entity 中需要 Job 類型,因此,我們希望定期運行的任務(wù),就需要實現(xiàn) Job 接口。同時,由于 Job 接口只有一個無參數(shù)無返回值的方法,為了使用方便,作者提供了一個類型
// 它通過簡單的實現(xiàn)Run()方法來實現(xiàn)Job接口 type FuncJob func() // 這樣,任何無參數(shù)無返回值的函數(shù),通過強制類型轉(zhuǎn)換為FuncJob,就可以當作Job來使用了,AddFunc方法就是這么做的 func (f FuncJob) Run() { f() }
6.2 Schedule
計劃接口:通過當前時間計算任務(wù)的下次執(zhí)行執(zhí)行時間,具體實現(xiàn)類可以根據(jù)實際需求實現(xiàn)。
每個實體包含一個調(diào)度器(Schedule)負責(zé)調(diào)度 Job 的執(zhí)行。它也是一個接口,Schedule 的具體實現(xiàn)通過解析 Cron
表達式得到。庫中提供了 Schedule 的兩個具體實現(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í)行的時間 Next(time.Time) time.Time }
6.2.1 SpecSchedule
從開始介紹的 Cron 表達式可以容易得知各個字段的意思,同時,對各種表達式的解析也會最終得到一個
SpecSchedule 的實例。庫中的 Parse 返回的其實就是 SpecSchedule 的實例(當然也就實現(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 方法實現(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)的時間間隔 Delay time.Duration }
這是一個簡單的循環(huán)調(diào)度器,如:每 5 分鐘。注意,最小單位是秒,不能比秒還小,比如毫秒。
實現(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ù)可以獲取該類型的實例,如下得到的是一個每 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 的實現(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
定時任務(wù)對象:保存執(zhí)行的任務(wù)Job、計算執(zhí)行時間。
// 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. ? ?? ?// 負責(zé)調(diào)度當前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í)行的時間 ?? ?Next time.Time ?? ?// Prev is the last time this job was run, or the zero time if never. ? ?? ?// 上一次執(zhí)行時間 ?? ?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)度管理:保存定時任務(wù)對象(Entry),調(diào)度任務(wù)執(zhí)行,提供新增、刪除接口(涉及關(guān)聯(lián)資源競爭)和暫停。
注意:
- Cron 結(jié)構(gòu)沒有導(dǎo)出任何成員。
- 有一個成員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實例暫停 add chan *Entry // 當Cron已經(jīng)運行了,增加新的Entity是通過add這個channel實現(xiàn)的 remove chan EntryID snapshot chan chan []Entry // 獲取當前所有entity的快照 running bool // 當已經(jīng)運行時為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、實例化主要方法說明
7.1 實例化
啟動時會開啟唯一協(xié)程執(zhí)行run方法,計算任務(wù)執(zhí)行時間,執(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. // 實例化時,成員使用的基本是默認值 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)度:計算下次執(zhí)行時間 -> 排序 -> 取最早執(zhí)行數(shù)據(jù) -> timer 等待,因為只有一個協(xié)程在執(zhí)行這個run的調(diào)
度,所以不存在資源競爭,不需要加鎖,另外考慮到執(zhí)行任務(wù)可能涉及阻塞,例如:IO操作,所以一般startJob方
法會開啟協(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標識Cron實例中的entry type EntryID int // 將job加入Cron中 // 如上所述,該方法只是簡單的通過FuncJob類型強制轉(zhuǎn)換cmd,然后調(diào)用AddJob方法 func (c *Cron) AddFunc(spec string, cmd func()) (EntryID, error)? // 將job加入Cron中 // 通過Parse函數(shù)解析cron表達式spec的到調(diào)度器實例(Schedule),之后調(diào)用c.Schedule方法 func (c *Cron) AddJob(spec string, cmd Job) (EntryID, error) // 獲取當前Cron總所有Entities的快照 func (c *Cron) Entries() []*Entry // Location獲取時區(qū)位置 func (c *Cron) Location() *time.Location? // Entry返回給定項的快照,如果找不到則返回nil func (c *Cron) Entry(id EntryID) Entry // 刪除將來運行的條目 func (c *Cron) Remove(id EntryID)? // 通過兩個參數(shù)實例化一個Entity,然后加入當前Cron中 // 注意: 如果當前Cron未運行,則直接將該entity加入Cron中 // 否則,通過add這個成員channel將entity加入正在運行的Cron中 func (c *Cron) Schedule(schedule Schedule, cmd Job) EntryID // 新啟動一個goroutine運行當前Cron func (c *Cron) Start() // 通過給stop成員發(fā)送一個struct{}{}來停止當前Cron,同時將running置為false // 從這里知道,stop只是通知Cron停止,因此往channel發(fā)一個值即可,而不關(guān)心值是多少 // 所以,成員stop定義為空struct func (c *Cron) Stop() // 運行cron調(diào)度程序,如果已經(jīng)在運行,則不運行op func (c *Cron) Run()
8、其它定時任務(wù)的實現(xiàn)
8.1 最簡單的定時任務(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實現(xiàn)定時任務(wù)
除了使用 cron 庫可以實現(xiàn)定時任務(wù),使用 time 庫也可以實現(xiàn)定時任務(wù)。
在Go語言中,可以使用 time 包提供的 Timer 和 Ticker 類型設(shè)置定時任務(wù)。Timer 用于在未來的某個時間點執(zhí)行
一次任務(wù),而 Ticker 則用于每隔一定時間執(zhí)行一次任務(wù)。
8.2.1 Timer啟動定時器
Timer 實現(xiàn)定時器,延遲執(zhí)行,這個定時器只會觸發(fā)一次。
下面是一個使用 Timer 設(shè)置定時任務(wù)的例子:
package main import ( ?? ?"fmt" ?? ?"time" ) func main() { ?? ?// 創(chuàng)建一個Timer實例,設(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停止定時器
使用 time.Stop() 停止定時器,通過向通道發(fā)送一個信號,通知定時器是否關(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重置定時器
package main import ( ?? ?"fmt" ?? ?"time" ) func main() { ?? ?fmt.Println("hello world", time.Now()) ?? ?// 創(chuàng)建一個定時器 ?? ?// 設(shè)置7秒后執(zhí)行一次 ?? ?myT := time.NewTimer(7 * time.Second) ?? ?// 重置定時器為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啟動定時器
Ticker 也是定時器,它是一個周期性的定時器。
package main import ( ?? ?"fmt" ?? ?"time" ) func main() { ?? ?// 創(chuàng)建一個Ticker實例,每隔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)建一個定時器,每隔1秒觸發(fā)一次 ?? ?ticker := time.NewTicker(1 * time.Second) ?? ?// 在函數(shù)退出時停止定時器 ?? ?defer ticker.Stop() ?? ?// timer實現(xiàn)定時器(延遲執(zhí)行),這個定時器只會觸發(fā)一次,所以想要執(zhí)行定時任務(wù)需要放在for循環(huán)中 ?? ?for { ?? ??? ?select { ?? ??? ?// 定時器觸發(fā)時執(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停止定時器
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庫
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語言定時任務(wù)的實現(xiàn)示例的文章就介紹到這了,更多相關(guān)Go語言定時任務(wù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
自己動手用Golang實現(xiàn)約瑟夫環(huán)算法的示例
這篇文章主要介紹了自己動手用Golang實現(xiàn)約瑟夫環(huán)算法的示例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12