Go語(yǔ)言中的定時(shí)器原理與實(shí)戰(zhàn)應(yīng)用
在Go語(yǔ)言中,定時(shí)器是并發(fā)編程中常用的工具之一。定時(shí)器可以用于監(jiān)控某個(gè)goroutine的運(yùn)行時(shí)間、定時(shí)打印日志、周期性執(zhí)行任務(wù)等多種場(chǎng)景。
Go標(biāo)準(zhǔn)庫(kù)提供了兩種主要的定時(shí)器:Timer(一次性定時(shí)器)和Ticker(周期性定時(shí)器)。本文將詳細(xì)介紹這兩種定時(shí)器的用法,并通過(guò)實(shí)際案例展示其應(yīng)用場(chǎng)景。
一、Timer定時(shí)器
Timer定時(shí)器是一種一次性定時(shí)器,即在未來(lái)某個(gè)時(shí)刻觸發(fā)的事件只會(huì)執(zhí)行一次。
Timer的結(jié)構(gòu)中包含一個(gè)Time類型的管道C,主要用于事件通知。
在未到達(dá)設(shè)定時(shí)間時(shí),管道內(nèi)沒(méi)有數(shù)據(jù)寫入,一直處于阻塞狀態(tài);到達(dá)設(shè)定時(shí)間后,會(huì)向管道內(nèi)寫入一個(gè)系統(tǒng)時(shí)間,觸發(fā)事件。
1. 創(chuàng)建Timer
使用time.NewTimer函數(shù)可以創(chuàng)建一個(gè)Timer定時(shí)器。該函數(shù)接受一個(gè)Duration類型的參數(shù),表示定時(shí)器的超時(shí)時(shí)間,并返回一個(gè)*Timer類型的指針。
看下源碼,Timer結(jié)構(gòu)體中,timer.C是一個(gè)時(shí)間類型的通道
package main import ( "fmt" "time" ) func main() { // func NewTimer(d Duration) *Timer timer := time.NewTimer(2 * time.Second) // 設(shè)置超時(shí)時(shí)間2秒 // 先打印下當(dāng)前時(shí)間 fmt.Println("當(dāng)前時(shí)間:", time.Now()) //我們可以打印下這個(gè)只讀通道的值 //timer.C就是我們?cè)诙x定時(shí)器的時(shí)候,存放的時(shí)間,等待對(duì)應(yīng)的時(shí)間?,F(xiàn)在這個(gè)就是根據(jù)當(dāng)前時(shí)間加2秒 fmt.Println("通道里面的值", <-timer.C) }
可以看到timer.C這個(gè)只讀通道里面的值,就是通過(guò)NewTimer設(shè)置時(shí)間間隔后的時(shí)間
因此,我們可以根據(jù)時(shí)間通道,來(lái)定時(shí)將來(lái)某個(gè)時(shí)間要做的事
package main import ( "fmt" "time" ) func main() { timer := time.NewTimer(2 * time.Second) // 設(shè)置超時(shí)時(shí)間2秒 <-timer.C //經(jīng)過(guò)兩秒后只想下面代碼 fmt.Println("after 2s Time out!") }
在上述代碼中,創(chuàng)建了一個(gè)超時(shí)時(shí)間為2秒的定時(shí)器,程序會(huì)阻塞在<-timer.C處,直到2秒后定時(shí)器觸發(fā),程序繼續(xù)執(zhí)行并打印“after 2s Time out!”。
2. 停止Timer
使用Stop方法可以停止一個(gè)Timer定時(shí)器。該方法返回一個(gè)布爾值,表示定時(shí)器是否在超時(shí)前被停止。
如果返回true,表示定時(shí)器在超時(shí)前被成功停止;如果返回false,表示定時(shí)器已經(jīng)超時(shí)或已經(jīng)被停止過(guò)。
package main import ( "fmt" "time" ) func main() { timer := time.NewTimer(2 * time.Second) // 設(shè)置超時(shí)時(shí)間2秒 //這里,創(chuàng)建定時(shí)器后,里面執(zhí)行了停止方法,肯定是在定時(shí)器超時(shí)前停止了,返回true res := timer.Stop() fmt.Println(res) // 輸出:true }
在上述代碼中,創(chuàng)建了一個(gè)超時(shí)時(shí)間為2秒的定時(shí)器,并立即調(diào)用Stop方法停止它。由于定時(shí)器還沒(méi)有超時(shí),所以Stop方法返回true。
3. 重置Timer
對(duì)于已經(jīng)過(guò)期或者是已經(jīng)停止的Timer,可以通過(guò)Reset方法重新激活它,并設(shè)置新的超時(shí)時(shí)間。Reset方法也返回一個(gè)布爾值,表示定時(shí)器是否在重置前已經(jīng)停止或過(guò)期。
package main import ( "fmt" "time" ) func main() { timer := time.NewTimer(2 * time.Second) <-timer.C fmt.Println("time out1") //經(jīng)過(guò)兩秒后,定時(shí)器超時(shí)了 res1 := timer.Stop() //此時(shí)再stop,得到的是false fmt.Printf("res1 is %t\n", res1) // 輸出:false //然后我們重置定時(shí)器。重置成3秒后超時(shí) timer.Reset(3 * time.Second) res2 := timer.Stop() //此時(shí)再stop,由于定時(shí)器沒(méi)超時(shí),得到的是true fmt.Printf("res2 is %t\n", res2) // 輸出:true }
在上述代碼中,首先創(chuàng)建了一個(gè)超時(shí)時(shí)間為2秒的定時(shí)器,并在超時(shí)后打印“time out1”。
然后調(diào)用Stop方法停止定時(shí)器,由于定時(shí)器已經(jīng)過(guò)期,所以Stop方法返回false。
接著調(diào)用Reset方法將定時(shí)器重新激活,并設(shè)置新的超時(shí)時(shí)間為3秒。
最后再次調(diào)用Stop方法停止定時(shí)器,由于此時(shí)定時(shí)器還沒(méi)有過(guò)期,所以Stop方法返回true。
4. time.AfterFunc
time.AfterFunc函數(shù)可以接受一個(gè)Duration類型的參數(shù)和一個(gè)函數(shù)f,返回一個(gè)*Timer類型的指針。在創(chuàng)建Timer之后,等待一段時(shí)間d,然后執(zhí)行函數(shù)f。
package main import ( "fmt" "time" ) func main() { duration := time.Duration(1) * time.Second f := func() { fmt.Println("f has been called after 1s by time.AfterFunc") } // func AfterFunc(d Duration, f func()) *Timer //等待duration時(shí)間后,執(zhí)行f函數(shù) //這里是經(jīng)過(guò)1秒后。執(zhí)行f函數(shù) timer := time.AfterFunc(duration, f) defer timer.Stop() time.Sleep(2 * time.Second) }
在上述代碼中,創(chuàng)建了一個(gè)超時(shí)時(shí)間為1秒的定時(shí)器,并在超時(shí)后執(zhí)行函數(shù)f。
使用defer語(yǔ)句確保在程序結(jié)束時(shí)停止定時(shí)器。程序會(huì)在1秒后打印“f has been called after 1s by time.AfterFunc”。
5. time.After
time.After函數(shù)會(huì)返回一個(gè)*Timer類型的管道,該管道會(huì)在經(jīng)過(guò)指定時(shí)間段d后寫入數(shù)據(jù)。調(diào)用這個(gè)函數(shù)相當(dāng)于實(shí)現(xiàn)了一個(gè)定時(shí)器。
package main import ( "fmt" "time" ) func main() { ch := make(chan string) go func() { time.Sleep(3 * time.Second) ch <- "test" }() //使用select,哪個(gè)先到來(lái),耗時(shí)時(shí)間短,執(zhí)行哪個(gè) select { case val := <-ch: fmt.Printf("val is %s\n", val) // 這個(gè)case是兩秒后執(zhí)行 // func After(d Duration) <-chan Time case <-time.After(2 * time.Second): fmt.Println("timeout!!!") } }
在上述代碼中,創(chuàng)建了一個(gè)管道ch,并在另一個(gè)goroutine中等待3秒后向管道寫入數(shù)據(jù)。
在主goroutine中使用select語(yǔ)句監(jiān)聽兩個(gè)管道:一個(gè)是剛剛創(chuàng)建的ch,另一個(gè)是time.After函數(shù)返回的管道c。
由于ch管道3秒后才會(huì)有數(shù)據(jù)寫入,而time.After函數(shù)是2秒超時(shí),所以2秒后select會(huì)先收到管道c里的數(shù)據(jù),執(zhí)行“timeout!!!”并退出。
二、Ticker定時(shí)器
Ticker定時(shí)器可以周期性地不斷觸發(fā)時(shí)間事件,不需要額外的Reset操作。Ticker的結(jié)構(gòu)中也包含一個(gè)Time類型的管道C,每隔固定時(shí)間段d就會(huì)向該管道發(fā)送當(dāng)前的時(shí)間,根據(jù)這個(gè)管道消息來(lái)觸發(fā)事件。
Ticker定時(shí)器是Go標(biāo)準(zhǔn)庫(kù)time包中的一個(gè)重要組件。它允許你每隔一定的時(shí)間間隔執(zhí)行一次指定的操作。Ticker定時(shí)器在創(chuàng)建時(shí)會(huì)啟動(dòng)一個(gè)后臺(tái)goroutine,該goroutine會(huì)按照指定的時(shí)間間隔不斷向一個(gè)通道(Channel)發(fā)送當(dāng)前的時(shí)間值。
1. 創(chuàng)建Ticker
要?jiǎng)?chuàng)建一個(gè)Ticker定時(shí)器,你可以使用time.NewTicker函數(shù)。這個(gè)函數(shù)接受一個(gè)time.Duration類型的參數(shù),表示時(shí)間間隔,并返回一個(gè)*time.Ticker類型的指針。
Ticker定時(shí)器的核心是一個(gè)通道(Channel),你可以通過(guò)監(jiān)聽這個(gè)通道來(lái)接收時(shí)間間隔到達(dá)的事件。
ticker := time.NewTicker(1 * time.Second)
上面的代碼創(chuàng)建了一個(gè)每隔1秒觸發(fā)一次的Ticker定時(shí)器。
2. 監(jiān)聽Ticker事件
要監(jiān)聽Ticker定時(shí)器的事件,你可以使用range關(guān)鍵字或者select語(yǔ)句來(lái)監(jiān)聽Ticker定時(shí)器的通道。每次時(shí)間間隔到達(dá)時(shí),Ticker定時(shí)器的通道都會(huì)接收到一個(gè)當(dāng)前的時(shí)間值。
for range ticker.C { // 在這里執(zhí)行周期性任務(wù) }
package main import ( "fmt" "time" ) func main() { ticker := time.NewTicker(1 * time.Second) //查看定時(shí)器數(shù)據(jù)類型 fmt.Printf("定時(shí)器數(shù)據(jù)類型%T\n", ticker) //啟動(dòng)協(xié)程來(lái)監(jiān)聽定時(shí)器觸發(fā)事件,通過(guò)time.Sleep函數(shù)來(lái)等待5秒鐘,然后調(diào)用ticker.Stop()函數(shù)來(lái)停止定時(shí)器。 //最后,輸出"定時(shí)器停止"表示定時(shí)器已經(jīng)成功停止。 go func() { for range ticker.C { fmt.Println("Ticker ticked") } }() //執(zhí)行5秒后,讓定時(shí)器停止 time.Sleep(5 * time.Second) ticker.Stop() fmt.Println("定時(shí)器停止") }
或者,如果你需要同時(shí)監(jiān)聽多個(gè)通道,你可以使用select語(yǔ)句:
select { case t := <-ticker.C: // 處理Ticker定時(shí)器事件 case <-stopChan: // 處理停止信號(hào) }
package main import ( "fmt" "time" ) func main() { ticker := time.NewTicker(1 * time.Second) // 創(chuàng)建一個(gè)每秒觸發(fā)一次的Ticker定時(shí)器 defer ticker.Stop() // 確保在main函數(shù)結(jié)束時(shí)停止定時(shí)器 for { select { case t := <-ticker.C: fmt.Println("Tick at", t) } } }
每秒執(zhí)行一次
3. 停止Ticker定時(shí)器
當(dāng)你不再需要Ticker定時(shí)器時(shí),你應(yīng)該調(diào)用它的Stop方法來(lái)停止它。停止Ticker定時(shí)器可以釋放與之關(guān)聯(lián)的資源,并防止不必要的goroutine繼續(xù)運(yùn)行。
ticker.Stop()
停止Ticker定時(shí)器后,它的通道將不再接收任何事件。
package main import ( "fmt" "time" ) func main() { ticker := time.NewTicker(500 * time.Millisecond) // 創(chuàng)建一個(gè)每500毫秒觸發(fā)一次的Ticker定時(shí)器 timeEnd := make(chan bool) // 用于停止Ticker定時(shí)器的通道 go func() { for { select { //當(dāng)達(dá)到設(shè)置的停止條件式,停止循環(huán) case <-timeEnd: fmt.Println("===結(jié)束任務(wù)") break case t := <-ticker.C: fmt.Println("==500毫秒響應(yīng)一次:", t) } } }() time.Sleep(5 * time.Second) // 主線程等待5秒鐘 ticker.Stop() // 停止Ticker定時(shí)器 timeEnd <- true // 發(fā)送結(jié)束信號(hào) fmt.Println("===定時(shí)任務(wù)結(jié)束===") }
持續(xù)執(zhí)行5秒后,定時(shí)器停止運(yùn)行
三、定時(shí)器應(yīng)用案例
1. 定時(shí)打印日志
Ticker定時(shí)器的一個(gè)常見應(yīng)用是定時(shí)打印日志。通過(guò)設(shè)置一個(gè)Ticker定時(shí)器,你可以每隔固定的時(shí)間間隔輸出一次日志信息,從而監(jiān)控程序的運(yùn)行狀態(tài)。
package main import ( "fmt" "time" ) func main() { ticker := time.NewTicker(5 * time.Second) defer ticker.Stop() // 確保程序結(jié)束時(shí)停止Ticker定時(shí)器 for range ticker.C { // 打印當(dāng)前時(shí)間作為日志 fmt.Println("Current time:", time.Now()) } }
5秒打印一次
在這個(gè)例子中,我們創(chuàng)建了一個(gè)每隔5秒觸發(fā)一次的Ticker定時(shí)器,并在一個(gè)無(wú)限循環(huán)中監(jiān)聽它的事件。
每次事件觸發(fā)時(shí),我們都會(huì)打印當(dāng)前的時(shí)間作為日志信息。注意,由于我們?cè)趍ain函數(shù)中使用了defer語(yǔ)句來(lái)確保Ticker定時(shí)器在程序結(jié)束時(shí)被停止,所以即使循環(huán)是無(wú)限的,程序也不會(huì)因?yàn)門icker定時(shí)器而泄漏資源。
然而,在實(shí)際應(yīng)用中,你可能需要在某個(gè)條件下提前停止Ticker定時(shí)器。這時(shí),你可以使用一個(gè)額外的通道來(lái)發(fā)送停止信號(hào):
package main import ( "fmt" "time" ) func main() { ticker := time.NewTicker(5 * time.Second) stopChan := make(chan struct{}) go func() { // 模擬一個(gè)運(yùn)行一段時(shí)間的任務(wù) time.Sleep(15 * time.Second) // 發(fā)送停止信號(hào) stopChan <- struct{}{} }() for { select { case t := <-ticker.C: fmt.Println("Tick at", t) case <-stopChan: fmt.Println("Ticker stopped") ticker.Stop() return } } }
在這個(gè)例子中,我們創(chuàng)建了一個(gè)額外的stopChan通道來(lái)發(fā)送停止信號(hào)。我們啟動(dòng)了一個(gè)goroutine來(lái)模擬一個(gè)運(yùn)行一段時(shí)間的任務(wù),并在任務(wù)完成后向stopChan發(fā)送一個(gè)停止信號(hào)。
在for循環(huán)中,我們使用select語(yǔ)句同時(shí)監(jiān)聽Ticker定時(shí)器的通道和stopChan通道。當(dāng)接收到停止信號(hào)時(shí),我們停止Ticker定時(shí)器并退出程序。
2. 周期性檢查系統(tǒng)狀態(tài)
Ticker定時(shí)器還可以用于周期性檢查系統(tǒng)狀態(tài)。例如,你可以每隔一段時(shí)間檢查一次服務(wù)器的負(fù)載、內(nèi)存使用情況或數(shù)據(jù)庫(kù)連接數(shù)等關(guān)鍵指標(biāo),并在發(fā)現(xiàn)異常時(shí)采取相應(yīng)的措施。
package main import ( "fmt" "math/rand" "time" ) // 模擬檢查系統(tǒng)狀態(tài)的函數(shù) func checkSystemStatus() { // 這里可以添加實(shí)際的檢查邏輯 // 例如:檢查CPU使用率、內(nèi)存使用情況等 // 這里我們隨機(jī)生成一個(gè)0到100之間的數(shù)作為模擬結(jié)果 status := rand.Intn(101) fmt.Printf("System status: %d\n", status) // 假設(shè)狀態(tài)大于80表示系統(tǒng)異常 if status > 80 { fmt.Println("Warning: System status is above normal!") // 這里可以添加處理異常的邏輯 // 例如:發(fā)送警報(bào)、重啟服務(wù)等 } } func main() { ticker := time.NewTicker(10 * time.Second) defer ticker.Stop() // 確保程序結(jié)束時(shí)停止Ticker定時(shí)器 for range ticker.C { checkSystemStatus() } }
在這個(gè)例子中,我們創(chuàng)建了一個(gè)每隔10秒觸發(fā)一次的Ticker定時(shí)器,并在一個(gè)無(wú)限循環(huán)中監(jiān)聽它的事件。
每次事件觸發(fā)時(shí),我們都會(huì)調(diào)用checkSystemStatus函數(shù)來(lái)模擬檢查系統(tǒng)狀態(tài)。checkSystemStatus函數(shù)會(huì)隨機(jī)生成一個(gè)0到100之間的數(shù)作為模擬結(jié)果,并根據(jù)結(jié)果判斷是否系統(tǒng)異常。
如果系統(tǒng)異常(即狀態(tài)大于80),則打印警告信息,并可以在這里添加處理異常的邏輯。
同樣地,你可以使用額外的通道來(lái)發(fā)送停止信號(hào),以便在需要時(shí)提前停止Ticker定時(shí)器。
四、總結(jié)
本文詳細(xì)介紹了Go語(yǔ)言中Timer和Ticker兩種定時(shí)器的用法,并通過(guò)實(shí)際案例展示了它們的應(yīng)用場(chǎng)景。Timer定時(shí)器適用于需要一次性觸發(fā)的事件,而Ticker定時(shí)器適用于需要周期性觸發(fā)的事件
到此這篇關(guān)于Go語(yǔ)言中的定時(shí)器原理與實(shí)戰(zhàn)應(yīng)用的文章就介紹到這了,更多相關(guān)Go語(yǔ)言 定時(shí)器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
在Visual Studio Code中配置GO開發(fā)環(huán)境的詳細(xì)教程
這篇文章主要介紹了在Visual Studio Code中配置GO開發(fā)環(huán)境的詳細(xì)教程,需要的朋友可以參考下2017-02-02關(guān)于go get 下載第三方包存儲(chǔ)路徑問(wèn)題
這篇文章主要介紹了關(guān)于go get 下載第三方包存儲(chǔ)路徑問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01golang中sync.Mutex的實(shí)現(xiàn)方法
本文主要介紹了golang中sync.Mutex的實(shí)現(xiàn)方法,mutex?主要有兩個(gè)?method:?Lock()?和?Unlock(),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04Go結(jié)構(gòu)體從基礎(chǔ)到應(yīng)用深度探索
本文深入探討了結(jié)構(gòu)體的定義、類型、字面量表示和使用方法,旨在為讀者呈現(xiàn)Go結(jié)構(gòu)體的全面視角,通過(guò)結(jié)構(gòu)體,開發(fā)者可以實(shí)現(xiàn)更加模塊化、高效的代碼設(shè)計(jì),這篇文章旨在為您提供關(guān)于結(jié)構(gòu)體的深入理解,助您更好地利用Go語(yǔ)言的強(qiáng)大功能2023-10-10Golang中int類型和字符串類型相互轉(zhuǎn)換的實(shí)現(xiàn)方法
在日常開發(fā)中,經(jīng)常需要將數(shù)字轉(zhuǎn)換為字符串或者將字符串轉(zhuǎn)換為數(shù)字,在 Golang 中,有一些很簡(jiǎn)便的方法可以實(shí)現(xiàn)這個(gè)功能,接下來(lái)就詳細(xì)講解一下如何實(shí)現(xiàn) int 類型和字符串類型之間的互相轉(zhuǎn)換,需要的朋友可以參考下2023-09-09GO語(yǔ)言開發(fā)終端命令行小工具改進(jìn)更新
這篇文章主要為大家介紹了GO語(yǔ)言開發(fā)終端命令行小工具的改進(jìn)更新,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01