Golang當(dāng)中的定時(shí)器實(shí)例詳解
前言
在平時(shí)寫(xiě)代碼的時(shí)候,我們經(jīng)常會(huì)遇到在將來(lái)某個(gè)時(shí)間點(diǎn)或者間隔一段時(shí)間重復(fù)執(zhí)行函數(shù)。這個(gè)時(shí)候我們就可以考慮使用定時(shí)器。本片文章主要介紹一下golang當(dāng)中的幾個(gè)常用的定時(shí)器。time.Timer,time.Ticker,time.After以及time.AfterFunc和time.Ticker的基本使用
定時(shí)器的基本使用
golang當(dāng)中的定時(shí)器有這個(gè)一次性的定時(shí)器(Timer)和周期性的定時(shí)器(Ticker).在平時(shí)的編程當(dāng)中經(jīng)常會(huì)使用timer當(dāng)中的ticker,AfterFunc定時(shí)器,而NewTicker是每隔多長(zhǎng)時(shí)間觸發(fā),NewTimer是等待多長(zhǎng)時(shí)間觸發(fā)一次請(qǐng)注意是只觸發(fā)一次。請(qǐng)注意一下兩者的區(qū)別。
下面我們來(lái)首先來(lái)使用一下這兩個(gè)定時(shí)器首先是這個(gè)Timer定時(shí)器
package main import ( "fmt" "time" ) func main() { myTimer := time.NewTimer(time.Second * 3) //初始化定時(shí)器 var i = 0 for { select { case <-myTimer.C: i++ fmt.Printf("the counter is%d", i) myTimer.Reset(time.Second * 3) //注意需要重新設(shè)置 } } myTimer.Stop() //不在使用需要將其停止 }
注意這個(gè)timer定時(shí)器超時(shí)之后需要重新進(jìn)行設(shè)置,才能重新觸發(fā)。如果上面的代碼我們沒(méi)有Reset,那么就會(huì)導(dǎo)致死鎖。其實(shí)我們也可以看看這個(gè)Timer是怎么是實(shí)現(xiàn)的
func NewTimer(d Duration) *Timer { c := make(chan Time, 1) t := &Timer{ C: c, // 信道 r: runtimeTimer{ when: when(d), // 觸發(fā)時(shí)間 f: sendTime, // 時(shí)間到了之后的調(diào)用函數(shù) arg: c, // 調(diào)用sendTime時(shí)的入?yún)? }, } startTimer(&t.r) // 把定時(shí)器的r字段放入由定時(shí)器維護(hù)協(xié)程維護(hù)的堆中 return t }
從上面的構(gòu)造函數(shù)中可以大概看出定時(shí)器的工作流程,這里面最重要的是runtimeTimer。構(gòu)造定時(shí)器的時(shí)候會(huì)把runtimeTimer放入由定時(shí)器維護(hù)協(xié)程維護(hù)的堆中,當(dāng)時(shí)間到了之后,維護(hù)協(xié)程把r從堆中移除,并調(diào)用r的sendTime函數(shù),sendTime的入?yún)⑹嵌〞r(shí)器的信道C??梢酝茢?,sendTime中執(zhí)行的邏輯應(yīng)該是向信道C中推送時(shí)間,通知上游系統(tǒng)時(shí)間到了,而事實(shí)正是如此:
func sendTime(c interface{}, seq uintptr) { // Non-blocking send of time on c. // Used in NewTimer, it cannot block anyway (buffer). // Used in NewTicker, dropping sends on the floor is // the desired behavior when the reader gets behind, // because the sends are periodic. select { case c.(chan Time) <- Now(): //時(shí)間到了之后把當(dāng)前時(shí)間放入信道中 default: } }
其實(shí)這個(gè)time.After就是對(duì)這個(gè)time.Timer的一個(gè)封裝,所以如果我們上面使用這個(gè)time.After那么會(huì)頻繁的創(chuàng)建time.Timer對(duì)象
下面我們?cè)趤?lái)看一下這個(gè)time.AterFunc()定時(shí)器。
Golang當(dāng)中的AfterFunc函數(shù)用于等待經(jīng)過(guò)時(shí)間,此后在其自己的協(xié)程當(dāng)中調(diào)用定義的函數(shù)f.函數(shù)在時(shí)間包下定義。下面我們一起看看如何使用這個(gè)
import ( "fmt" "time" ) func main() { f := func() { fmt.Println("the func is call after 3 second") } myTime := time.AfterFunc(time.Second*3, f) defer myTime.Stop() //定時(shí)器不用了需要關(guān)閉 time.Sleep(time.Second * 4) }
下面我們?cè)诳纯催@個(gè)time.NewTicker定時(shí)器的使用
package main import ( "fmt" "time" ) func main() { mytick := time.NewTicker(time.Second * 2) defer mytick.Stop() //定時(shí)器不用了需要關(guān)閉 done := make(chan struct{}) go func() { for { time.Sleep(time.Second * 10) done <- struct{}{} } }() for { select { case <-done: fmt.Println("done!!!!") return case t := <-mytick.C: fmt.Printf("the curtime is %v\n", t) } } }
下面我們來(lái)看一下這個(gè)陷阱,這個(gè)需要注意
package main import ( "fmt" "time" ) func main() { var count int for { select { case <-time.Tick(time.Second * 1): fmt.Println("case1") count++ fmt.Println("count--->", count) case <-time.Tick(time.Second * 2): fmt.Println("case2") count++ fmt.Println("count--->", count) } } }
這個(gè)代碼是有陷阱的,下面我們來(lái)看看這個(gè)運(yùn)行結(jié)果是什么?
可見(jiàn) case2 永遠(yuǎn)沒(méi)有被執(zhí)行到,問(wèn)題就出在代碼邏輯上,首先看time.Tick方法。我們可以看一下這個(gè)方法就知道
func Tick(d Duration) <-chan Time { if d <= 0 { return nil } return NewTicker(d).C }
它每次都會(huì)創(chuàng)建一個(gè)新的定時(shí)器,隨著 for 循環(huán)進(jìn)行, select 始終監(jiān)聽(tīng)兩個(gè)新創(chuàng)建的定時(shí)器,老的定時(shí)器被拋棄掉了,也就不會(huì)去讀取老定時(shí)器中的通道。
select 可以同時(shí)監(jiān)聽(tīng)多個(gè)通道,誰(shuí)先到達(dá)就先讀取誰(shuí),如果同時(shí)有多個(gè)通道有消息到達(dá),那么會(huì)隨機(jī)讀取一個(gè)通道,其他的通道由于沒(méi)有被讀取,所以數(shù)據(jù)不會(huì)丟失,需要循環(huán)調(diào)用 select 來(lái)讀取剩下的通道。
總結(jié):
- tick創(chuàng)建完成之后,不是馬上有一個(gè)tick.第一個(gè)tick在你設(shè)置的多少秒之后才會(huì)進(jìn)行創(chuàng)建
- golang當(dāng)中的定時(shí)器實(shí)質(zhì)上是這個(gè)單項(xiàng)的管道
- time.NewTicker會(huì)定時(shí)觸發(fā)任務(wù),當(dāng)下一次執(zhí)行到來(lái)而當(dāng)前任務(wù)畫(huà)面執(zhí)行完,會(huì)等待當(dāng)前任務(wù)執(zhí)行完畢在進(jìn)行下一次任務(wù)。
- Ticker和Timer的不同之處是,Ticker時(shí)間到達(dá)之后不需要人為的調(diào)用Reset方法來(lái)重新設(shè)置時(shí)間
到此這篇關(guān)于Golang當(dāng)中的定時(shí)器的文章就介紹到這了,更多相關(guān)Golang定時(shí)器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決電腦用GoLand太卡將VsCode定制成Go IDE步驟過(guò)程
這篇文章主要為大家介紹了解決電腦用GoLand太卡,將VsCode定制成Go IDE步驟過(guò)程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11golang?日志庫(kù)ZAP[uber-go?zap]示例詳解
ZAP是由Uber開(kāi)源的高性能Go語(yǔ)言日志庫(kù),支持多種日志級(jí)別及基本信息打印,雖然ZAP本身不支持日志分割,但可以結(jié)合lumberjack進(jìn)行日志切割,實(shí)現(xiàn)日志按文件大小、時(shí)間或間隔切割等功能,ZAP提供Logger和SugaredLogger兩種日志記錄器2024-10-10Go語(yǔ)言提升開(kāi)發(fā)效率的語(yǔ)法糖技巧分享
每門(mén)語(yǔ)言都有自己的語(yǔ)法糖,像java的語(yǔ)法糖就有方法變長(zhǎng)參數(shù)、拆箱與裝箱、枚舉、for-each等等,Go語(yǔ)言也不例外。本文就來(lái)介紹一些Go語(yǔ)言的語(yǔ)法糖,需要的可以參考一下2022-07-07go 代碼的調(diào)試---打印調(diào)用堆棧的實(shí)例
下面小編就為大家?guī)?lái)一篇go 代碼的調(diào)試---打印調(diào)用堆棧的實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-10-10