很有幸得到公司信任,采用新的語言進行一些底層服務的開發(fā),在實現功能的同時,也獲得了一些感悟,因此在這記錄一下,方便自己查看也可以共享給大家。
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 Time
r runtimeTimer
}
原來是一個channel,其實有GO基礎的都知道,GO的運算符當出現的->或者-的時候,必然是有一端是指channel。按照上面的例子來看,就是阻塞在一個for循環(huán)內,等待到了定時器的C從channel出來,當獲取到值的時候,進行想要的操作。
設計我們的定時任務隊列
我的需求
當時我的需求是這樣,我需要接收到客戶端的請求并產生一個定時任務,會在固定時間執(zhí)行,可能是一次,也可能是多次,也可能到指定時間自動停止,可能當任務終止的時候,我還要能停止掉。
具體我畫了個流程圖,差不多如下,畫圖水平有限,請見諒。

定義結構
type OnceCron struct {
tasks []*Task //任務的列隊
add chan *Task //當遭遇到新任務的時候
remove chan string //當遭遇到刪除任務的時候
stop chan struct{} //當遇到停止信號的時候
Logger *log.Logger //日志
}
type Job interface {
Run() //執(zhí)行接口
}
type Task struct {
Job Job //要執(zhí)行的任務
Uuid string //任務標識,刪除時用
RunTime int64 //執(zhí)行時間
Spacing int64 //間隔時間
EndTime int64 //結束時間
Number int //總共要次數
}
隊列實現
首先,我們要獲得一個隊列任務
func NewCron() *OnceCron 常規(guī)操作,為了節(jié)省篇幅,我就不寫出來,具體可以看源碼,貼在了底部。
然后,開始定時器隊列的運行,一般,都會命名為Start。那么就有一個問題,我們剛開始啟動程序的時候,這個時候是沒有任務隊列,那豈不是for{ select{}}在等待個毛毛球?所以,我們需要在Start的時候添加一個默認的任務, 我是這么做的,添加了一個一小時執(zhí)行一次的重復隊列,防止隊列退出。
func (one *OnceCron) Start() {
//初始化的時候加入一個一年的長定時器,間隔1小時執(zhí)行一次
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() //協成執(zhí)行 防止主進程被阻塞
}
執(zhí)行部分應該是重點的,我的理解是,分成三部:
- 首先獲得一個最先執(zhí)行的任務
- 然后產生一個定時器,用于執(zhí)行任務
- 進行阻塞判斷,獲取我們要進行的操作
func (one *OnceCron) run() {
for {
//第一步 獲取任務
now := time.Now() //獲取到當前時間
task, key := one.GetTask() //獲取最近的一個任務的執(zhí)行時間
i64 := task.RunTime - now.Unix() //任務執(zhí)行和當前時間的差
var d time.Duration
if i64 0 { //如果任務時間已過期,將執(zhí)行時間改成現在并且利馬執(zhí)行
one.tasks[key].RunTime = now.Unix()
one.doAndReset(key)
continue
} else { //否則,獲取距離執(zhí)行開始的間隔時間
d = time.Unix(task.RunTime, 0).Sub(now)
}
//第二步 產生定時器
timer := time.NewTimer(d)
//第三步 捕獲定時器或者其他事件
for {
select {
//當定時器到了執(zhí)行時間時,執(zhí)行當前任務并關閉定時器
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 一個任務隊列
應用地址:一個應用于谷歌消息推送的轉發(fā)中間件
參考源碼:GOLANG實現crontab功能
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
您可能感興趣的文章:- Golang定時器的2種實現方法與區(qū)別
- golang定時器和超時的使用詳解
- Golang 定時器(Timer 和 Ticker),這篇文章就夠了
- Golang中定時器的陷阱詳解
- golang中定時器cpu使用率高的現象詳析
- golang time包下定時器的實現方法
- Golang 定時器的終止與重置實現