這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
golang中定時器
golang中提供了2種定時器timer和ticker(如果JS很熟悉的話應該會很瞭解),分別是一次性定時器和重複任務定時器。
一般用法:
func main() { input := make(chan interface{}) //producer - produce the messages go func() { for i := 0; i < 5; i++ { input <- i } input <- "hello, world" }() t1 := time.NewTimer(time.Second * 5) t2 := time.NewTimer(time.Second * 10) for { select { //consumer - consume the messages case msg := <-input: fmt.Println(msg) case <-t1.C: println("5s timer") t1.Reset(time.Second * 5) case <-t2.C: println("10s timer") t2.Reset(time.Second * 10) } }}
源碼觀察
這個C是啥,我們去源碼看看,以timer為例:
type Timer struct {C <-chan Timer runtimeTimer}
原來是一個channel,其實有GO基礎的都知道,GO的運算子當出現的->或者<-的時候,必然是有一端是指channel。按照上面的例子來看,就是阻塞在一個for迴圈內,等待到了定時器的C從channel出來,當擷取到值的時候,進行想要的操作。
設計我們的定時任務隊列
我的需求
當時我的需求是這樣,我需要接收到用戶端的請求併產生一個定時任務,會在固定時間執行,可能是一次,也可能是多次,也可能到指定時間自動停止,可能當任務終止的時候,我還要能停止掉。
具體我畫了個流程圖,差不多如下,畫圖水平有限,請見諒。
定義結構
type OnceCron struct {tasks []*Task //任務的列隊add chan *Task //當遭遇到新任務的時候remove chan string //當遭遇到刪除任務的時候stop chan struct{} //當遇到停止訊號的時候Logger *log.Logger //日誌 }type Job interface {Run() //執行介面}type Task struct { Job Job //要執行的任務 Uuid string //任務標識,刪除時用RunTime int64 //執行時間Spacing int64 //間隔時間EndTime int64 //結束時間Number int //總共要次數}
隊列實現
首先,我們要獲得一個隊列任務
func NewCron() *OnceCron 常規操作,為了節省篇幅,我就不寫出來,具體可以看源碼,貼在了底部。
然後,開始定時器隊列的運行,一般,都會命名為Start。那麼就有一個問題,我們剛開始啟動程式的時候,這個時候是沒有任務隊列,那豈不是for{ select{}}在等待個毛毛球?所以,我們需要在Start的時候添加一個預設的任務,我是這麼做的,添加了一個一小時執行一次的重複隊列,防止隊列退出。
func (one *OnceCron) Start() {//初始化的時候加入一個一年的長定時器,間隔1小時執行一次task := getTaskWithFuncSpacing(3600, time.Now().Add(time.Hour*24*365).Unix() , func() {log.Println("It's a Hour timer!")}) //為了代碼格式markdown 裡面有個括弧我改成全形了one.tasks = append(one.tasks, task)go one.run() //協成執行 防止主進程被阻塞}
執行部分應該是重點的,我的理解是,分成三部:
- 首先獲得一個最先執行的任務
- 然後產生一個定時器,用於執行任務
- 進行阻塞判斷,擷取我們要進行的操作
func (one *OnceCron) run() {for { //第一步 擷取任務now := time.Now() //擷取到目前時間task, key := one.GetTask() //擷取最近的一個任務的執行時間i64 := task.RunTime - now.Unix() //任務執行和目前時間的差var d time.Durationif i64 < 0 { //如果任務時間已到期,將執行時間改成現在並且利馬執行one.tasks[key].RunTime = now.Unix() one.doAndReset(key) continue} else { //否則,擷取距離執行開始的間隔時間d = time.Unix(task.RunTime, 0).Sub(now)} //第二步 產生定時器timer := time.NewTimer(d) //第三步 捕獲定時器或者其他事件for {select { //當定時器到了執行時間時,執行當前任務並關閉定時器case <-timer.C:one.doAndReset(key)if task != nil {go task.Job.Run()timer.Stop()}//當外部添加了任務時,關閉當前定時器case <-one.add:timer.Stop()//當外部要刪除一個任務時,刪除ID為uuidstr的任務case uuidstr := <-one.remove:one.removeTask(uuidstr)timer.Stop()//當遇到要關閉整個定時器任務時case <-one.stop:timer.Stop()return}break}}}
後記
這個文章純粹為筆記分析類的文章,旨在分析我碰到一個需求是如何通過分析過程來產生我們需要的代碼的。
源碼地址:
timing 一個任務隊列
應用地址:
一個應用於Google訊息推送的轉寄中介軟體
參考源碼:
GOLANG實現crontab功能