Go語言中的定時器原理與實戰(zhàn)應(yīng)用
在Go語言中,定時器是并發(fā)編程中常用的工具之一。定時器可以用于監(jiān)控某個goroutine的運行時間、定時打印日志、周期性執(zhí)行任務(wù)等多種場景。
Go標(biāo)準(zhǔn)庫提供了兩種主要的定時器:Timer(一次性定時器)和Ticker(周期性定時器)。本文將詳細(xì)介紹這兩種定時器的用法,并通過實際案例展示其應(yīng)用場景。
一、Timer定時器
Timer定時器是一種一次性定時器,即在未來某個時刻觸發(fā)的事件只會執(zhí)行一次。
Timer的結(jié)構(gòu)中包含一個Time類型的管道C,主要用于事件通知。
在未到達(dá)設(shè)定時間時,管道內(nèi)沒有數(shù)據(jù)寫入,一直處于阻塞狀態(tài);到達(dá)設(shè)定時間后,會向管道內(nèi)寫入一個系統(tǒng)時間,觸發(fā)事件。
1. 創(chuàng)建Timer
使用time.NewTimer函數(shù)可以創(chuàng)建一個Timer定時器。該函數(shù)接受一個Duration類型的參數(shù),表示定時器的超時時間,并返回一個*Timer類型的指針。
看下源碼,Timer結(jié)構(gòu)體中,timer.C是一個時間類型的通道

package main
import (
"fmt"
"time"
)
func main() {
// func NewTimer(d Duration) *Timer
timer := time.NewTimer(2 * time.Second) // 設(shè)置超時時間2秒
// 先打印下當(dāng)前時間
fmt.Println("當(dāng)前時間:", time.Now())
//我們可以打印下這個只讀通道的值
//timer.C就是我們在定義定時器的時候,存放的時間,等待對應(yīng)的時間。現(xiàn)在這個就是根據(jù)當(dāng)前時間加2秒
fmt.Println("通道里面的值", <-timer.C)
}
可以看到timer.C這個只讀通道里面的值,就是通過NewTimer設(shè)置時間間隔后的時間

因此,我們可以根據(jù)時間通道,來定時將來某個時間要做的事
package main
import (
"fmt"
"time"
)
func main() {
timer := time.NewTimer(2 * time.Second) // 設(shè)置超時時間2秒
<-timer.C
//經(jīng)過兩秒后只想下面代碼
fmt.Println("after 2s Time out!")
}

在上述代碼中,創(chuàng)建了一個超時時間為2秒的定時器,程序會阻塞在<-timer.C處,直到2秒后定時器觸發(fā),程序繼續(xù)執(zhí)行并打印“after 2s Time out!”。
2. 停止Timer
使用Stop方法可以停止一個Timer定時器。該方法返回一個布爾值,表示定時器是否在超時前被停止。
如果返回true,表示定時器在超時前被成功停止;如果返回false,表示定時器已經(jīng)超時或已經(jīng)被停止過。

package main
import (
"fmt"
"time"
)
func main() {
timer := time.NewTimer(2 * time.Second) // 設(shè)置超時時間2秒
//這里,創(chuàng)建定時器后,里面執(zhí)行了停止方法,肯定是在定時器超時前停止了,返回true
res := timer.Stop()
fmt.Println(res) // 輸出:true
}
在上述代碼中,創(chuàng)建了一個超時時間為2秒的定時器,并立即調(diào)用Stop方法停止它。由于定時器還沒有超時,所以Stop方法返回true。
3. 重置Timer
對于已經(jīng)過期或者是已經(jīng)停止的Timer,可以通過Reset方法重新激活它,并設(shè)置新的超時時間。Reset方法也返回一個布爾值,表示定時器是否在重置前已經(jīng)停止或過期。

package main
import (
"fmt"
"time"
)
func main() {
timer := time.NewTimer(2 * time.Second)
<-timer.C
fmt.Println("time out1")
//經(jīng)過兩秒后,定時器超時了
res1 := timer.Stop()
//此時再stop,得到的是false
fmt.Printf("res1 is %t\n", res1) // 輸出:false
//然后我們重置定時器。重置成3秒后超時
timer.Reset(3 * time.Second)
res2 := timer.Stop()
//此時再stop,由于定時器沒超時,得到的是true
fmt.Printf("res2 is %t\n", res2) // 輸出:true
}

在上述代碼中,首先創(chuàng)建了一個超時時間為2秒的定時器,并在超時后打印“time out1”。
然后調(diào)用Stop方法停止定時器,由于定時器已經(jīng)過期,所以Stop方法返回false。
接著調(diào)用Reset方法將定時器重新激活,并設(shè)置新的超時時間為3秒。
最后再次調(diào)用Stop方法停止定時器,由于此時定時器還沒有過期,所以Stop方法返回true。
4. time.AfterFunc
time.AfterFunc函數(shù)可以接受一個Duration類型的參數(shù)和一個函數(shù)f,返回一個*Timer類型的指針。在創(chuàng)建Timer之后,等待一段時間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時間后,執(zhí)行f函數(shù)
//這里是經(jīng)過1秒后。執(zhí)行f函數(shù)
timer := time.AfterFunc(duration, f)
defer timer.Stop()
time.Sleep(2 * time.Second)
}
在上述代碼中,創(chuàng)建了一個超時時間為1秒的定時器,并在超時后執(zhí)行函數(shù)f。
使用defer語句確保在程序結(jié)束時停止定時器。程序會在1秒后打印“f has been called after 1s by time.AfterFunc”。
5. time.After
time.After函數(shù)會返回一個*Timer類型的管道,該管道會在經(jīng)過指定時間段d后寫入數(shù)據(jù)。調(diào)用這個函數(shù)相當(dāng)于實現(xiàn)了一個定時器。

package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan string)
go func() {
time.Sleep(3 * time.Second)
ch <- "test"
}()
//使用select,哪個先到來,耗時時間短,執(zhí)行哪個
select {
case val := <-ch:
fmt.Printf("val is %s\n", val)
// 這個case是兩秒后執(zhí)行
// func After(d Duration) <-chan Time
case <-time.After(2 * time.Second):
fmt.Println("timeout!!!")
}
}

在上述代碼中,創(chuàng)建了一個管道ch,并在另一個goroutine中等待3秒后向管道寫入數(shù)據(jù)。
在主goroutine中使用select語句監(jiān)聽兩個管道:一個是剛剛創(chuàng)建的ch,另一個是time.After函數(shù)返回的管道c。
由于ch管道3秒后才會有數(shù)據(jù)寫入,而time.After函數(shù)是2秒超時,所以2秒后select會先收到管道c里的數(shù)據(jù),執(zhí)行“timeout!!!”并退出。
二、Ticker定時器
Ticker定時器可以周期性地不斷觸發(fā)時間事件,不需要額外的Reset操作。Ticker的結(jié)構(gòu)中也包含一個Time類型的管道C,每隔固定時間段d就會向該管道發(fā)送當(dāng)前的時間,根據(jù)這個管道消息來觸發(fā)事件。
Ticker定時器是Go標(biāo)準(zhǔn)庫time包中的一個重要組件。它允許你每隔一定的時間間隔執(zhí)行一次指定的操作。Ticker定時器在創(chuàng)建時會啟動一個后臺goroutine,該goroutine會按照指定的時間間隔不斷向一個通道(Channel)發(fā)送當(dāng)前的時間值。
1. 創(chuàng)建Ticker
要創(chuàng)建一個Ticker定時器,你可以使用time.NewTicker函數(shù)。這個函數(shù)接受一個time.Duration類型的參數(shù),表示時間間隔,并返回一個*time.Ticker類型的指針。
Ticker定時器的核心是一個通道(Channel),你可以通過監(jiān)聽這個通道來接收時間間隔到達(dá)的事件。
ticker := time.NewTicker(1 * time.Second)
上面的代碼創(chuàng)建了一個每隔1秒觸發(fā)一次的Ticker定時器。
2. 監(jiān)聽Ticker事件
要監(jiān)聽Ticker定時器的事件,你可以使用range關(guān)鍵字或者select語句來監(jiān)聽Ticker定時器的通道。每次時間間隔到達(dá)時,Ticker定時器的通道都會接收到一個當(dāng)前的時間值。
for range ticker.C {
// 在這里執(zhí)行周期性任務(wù)
}
package main
import (
"fmt"
"time"
)
func main() {
ticker := time.NewTicker(1 * time.Second)
//查看定時器數(shù)據(jù)類型
fmt.Printf("定時器數(shù)據(jù)類型%T\n", ticker)
//啟動協(xié)程來監(jiān)聽定時器觸發(fā)事件,通過time.Sleep函數(shù)來等待5秒鐘,然后調(diào)用ticker.Stop()函數(shù)來停止定時器。
//最后,輸出"定時器停止"表示定時器已經(jīng)成功停止。
go func() {
for range ticker.C {
fmt.Println("Ticker ticked")
}
}()
//執(zhí)行5秒后,讓定時器停止
time.Sleep(5 * time.Second)
ticker.Stop()
fmt.Println("定時器停止")
}

或者,如果你需要同時監(jiān)聽多個通道,你可以使用select語句:
select {
case t := <-ticker.C:
// 處理Ticker定時器事件
case <-stopChan:
// 處理停止信號
}
package main
import (
"fmt"
"time"
)
func main() {
ticker := time.NewTicker(1 * time.Second) // 創(chuàng)建一個每秒觸發(fā)一次的Ticker定時器
defer ticker.Stop() // 確保在main函數(shù)結(jié)束時停止定時器
for {
select {
case t := <-ticker.C:
fmt.Println("Tick at", t)
}
}
}
每秒執(zhí)行一次

3. 停止Ticker定時器
當(dāng)你不再需要Ticker定時器時,你應(yīng)該調(diào)用它的Stop方法來停止它。停止Ticker定時器可以釋放與之關(guān)聯(lián)的資源,并防止不必要的goroutine繼續(xù)運行。
ticker.Stop()
停止Ticker定時器后,它的通道將不再接收任何事件。
package main
import (
"fmt"
"time"
)
func main() {
ticker := time.NewTicker(500 * time.Millisecond) // 創(chuàng)建一個每500毫秒觸發(fā)一次的Ticker定時器
timeEnd := make(chan bool) // 用于停止Ticker定時器的通道
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定時器
timeEnd <- true // 發(fā)送結(jié)束信號
fmt.Println("===定時任務(wù)結(jié)束===")
}
持續(xù)執(zhí)行5秒后,定時器停止運行

三、定時器應(yīng)用案例
1. 定時打印日志
Ticker定時器的一個常見應(yīng)用是定時打印日志。通過設(shè)置一個Ticker定時器,你可以每隔固定的時間間隔輸出一次日志信息,從而監(jiān)控程序的運行狀態(tài)。
package main
import (
"fmt"
"time"
)
func main() {
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop() // 確保程序結(jié)束時停止Ticker定時器
for range ticker.C {
// 打印當(dāng)前時間作為日志
fmt.Println("Current time:", time.Now())
}
}
5秒打印一次

在這個例子中,我們創(chuàng)建了一個每隔5秒觸發(fā)一次的Ticker定時器,并在一個無限循環(huán)中監(jiān)聽它的事件。
每次事件觸發(fā)時,我們都會打印當(dāng)前的時間作為日志信息。注意,由于我們在main函數(shù)中使用了defer語句來確保Ticker定時器在程序結(jié)束時被停止,所以即使循環(huán)是無限的,程序也不會因為Ticker定時器而泄漏資源。
然而,在實際應(yīng)用中,你可能需要在某個條件下提前停止Ticker定時器。這時,你可以使用一個額外的通道來發(fā)送停止信號:
package main
import (
"fmt"
"time"
)
func main() {
ticker := time.NewTicker(5 * time.Second)
stopChan := make(chan struct{})
go func() {
// 模擬一個運行一段時間的任務(wù)
time.Sleep(15 * time.Second)
// 發(fā)送停止信號
stopChan <- struct{}{}
}()
for {
select {
case t := <-ticker.C:
fmt.Println("Tick at", t)
case <-stopChan:
fmt.Println("Ticker stopped")
ticker.Stop()
return
}
}
}

在這個例子中,我們創(chuàng)建了一個額外的stopChan通道來發(fā)送停止信號。我們啟動了一個goroutine來模擬一個運行一段時間的任務(wù),并在任務(wù)完成后向stopChan發(fā)送一個停止信號。
在for循環(huán)中,我們使用select語句同時監(jiān)聽Ticker定時器的通道和stopChan通道。當(dāng)接收到停止信號時,我們停止Ticker定時器并退出程序。
2. 周期性檢查系統(tǒng)狀態(tài)
Ticker定時器還可以用于周期性檢查系統(tǒng)狀態(tài)。例如,你可以每隔一段時間檢查一次服務(wù)器的負(fù)載、內(nèi)存使用情況或數(shù)據(jù)庫連接數(shù)等關(guān)鍵指標(biāo),并在發(fā)現(xiàn)異常時采取相應(yīng)的措施。
package main
import (
"fmt"
"math/rand"
"time"
)
// 模擬檢查系統(tǒng)狀態(tài)的函數(shù)
func checkSystemStatus() {
// 這里可以添加實際的檢查邏輯
// 例如:檢查CPU使用率、內(nèi)存使用情況等
// 這里我們隨機(jī)生成一個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ā)送警報、重啟服務(wù)等
}
}
func main() {
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop() // 確保程序結(jié)束時停止Ticker定時器
for range ticker.C {
checkSystemStatus()
}
}

在這個例子中,我們創(chuàng)建了一個每隔10秒觸發(fā)一次的Ticker定時器,并在一個無限循環(huán)中監(jiān)聽它的事件。
每次事件觸發(fā)時,我們都會調(diào)用checkSystemStatus函數(shù)來模擬檢查系統(tǒng)狀態(tài)。checkSystemStatus函數(shù)會隨機(jī)生成一個0到100之間的數(shù)作為模擬結(jié)果,并根據(jù)結(jié)果判斷是否系統(tǒng)異常。
如果系統(tǒng)異常(即狀態(tài)大于80),則打印警告信息,并可以在這里添加處理異常的邏輯。
同樣地,你可以使用額外的通道來發(fā)送停止信號,以便在需要時提前停止Ticker定時器。
四、總結(jié)
本文詳細(xì)介紹了Go語言中Timer和Ticker兩種定時器的用法,并通過實際案例展示了它們的應(yīng)用場景。Timer定時器適用于需要一次性觸發(fā)的事件,而Ticker定時器適用于需要周期性觸發(fā)的事件
到此這篇關(guān)于Go語言中的定時器原理與實戰(zhàn)應(yīng)用的文章就介紹到這了,更多相關(guān)Go語言 定時器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
在Visual Studio Code中配置GO開發(fā)環(huán)境的詳細(xì)教程
這篇文章主要介紹了在Visual Studio Code中配置GO開發(fā)環(huán)境的詳細(xì)教程,需要的朋友可以參考下2017-02-02
Go結(jié)構(gòu)體從基礎(chǔ)到應(yīng)用深度探索
本文深入探討了結(jié)構(gòu)體的定義、類型、字面量表示和使用方法,旨在為讀者呈現(xiàn)Go結(jié)構(gòu)體的全面視角,通過結(jié)構(gòu)體,開發(fā)者可以實現(xiàn)更加模塊化、高效的代碼設(shè)計,這篇文章旨在為您提供關(guān)于結(jié)構(gòu)體的深入理解,助您更好地利用Go語言的強(qiáng)大功能2023-10-10
Golang中int類型和字符串類型相互轉(zhuǎn)換的實現(xiàn)方法
在日常開發(fā)中,經(jīng)常需要將數(shù)字轉(zhuǎn)換為字符串或者將字符串轉(zhuǎn)換為數(shù)字,在 Golang 中,有一些很簡便的方法可以實現(xiàn)這個功能,接下來就詳細(xì)講解一下如何實現(xiàn) int 類型和字符串類型之間的互相轉(zhuǎn)換,需要的朋友可以參考下2023-09-09

