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)文章希望大家以后多多支持腳本之家!
- Golang cron 定時(shí)器和定時(shí)任務(wù)的使用場(chǎng)景
- golang croncli 定時(shí)器命令詳解
- 用golang實(shí)現(xiàn)一個(gè)定時(shí)器任務(wù)隊(duì)列實(shí)例
- golang time包下定時(shí)器的實(shí)現(xiàn)方法
- Golang中定時(shí)器的陷阱詳解
- Golang 定時(shí)器的終止與重置實(shí)現(xiàn)
- golang定時(shí)器和超時(shí)的使用詳解
- Golang定時(shí)器的2種實(shí)現(xiàn)方法與區(qū)別
- 從源碼解析golang Timer定時(shí)器體系
- 詳解如何解決golang定時(shí)器引發(fā)的id重復(fù)問(wèn)題
- golang定時(shí)器案例詳解
- Golang第三方庫(kù)robfig/cron定時(shí)器庫(kù)用法小結(jié)
相關(guān)文章
golang并發(fā)執(zhí)行的幾種方式小結(jié)
本文主要介紹了golang并發(fā)執(zhí)行的幾種方式小結(jié),主要包括了Channel,WaitGroup ,Context,使用這三種機(jī)制中的一種或者多種可以達(dá)到并發(fā)控制很好的效果,具有一定的參考價(jià)值,感興趣的可以了解一下2023-08-08
Go實(shí)現(xiàn)生產(chǎn)隨機(jī)密碼的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何利用Go實(shí)現(xiàn)生產(chǎn)隨機(jī)密碼的,文中的示例代碼簡(jiǎn)潔易懂,具有一定的借鑒價(jià)值,有需要的小伙伴可以參考一下2023-09-09
Go語(yǔ)言超時(shí)退出的三種實(shí)現(xiàn)方式總結(jié)
這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言中超時(shí)退出的三種實(shí)現(xiàn)方式,文中的示例代碼簡(jiǎn)潔易懂,對(duì)我們深入了解Go語(yǔ)言有一定的幫助,需要的可以了解一下2023-06-06
Golang打印復(fù)雜結(jié)構(gòu)體兩種方法詳解
在?Golang?語(yǔ)言開(kāi)發(fā)中,我們經(jīng)常會(huì)使用結(jié)構(gòu)體類(lèi)型,如果我們使用的結(jié)構(gòu)體類(lèi)型的變量包含指針類(lèi)型的字段,我們?cè)谟涗浫罩镜臅r(shí)候,指針類(lèi)型的字段的值是指針地址,將會(huì)給我們?debug?代碼造成不便2022-10-10

