Go語言中的定時器原理與實戰(zhàn)應用
在Go語言中,定時器是并發(fā)編程中常用的工具之一。定時器可以用于監(jiān)控某個goroutine的運行時間、定時打印日志、周期性執(zhí)行任務等多種場景。
Go標準庫提供了兩種主要的定時器:Timer(一次性定時器)和Ticker(周期性定時器)。本文將詳細介紹這兩種定時器的用法,并通過實際案例展示其應用場景。
一、Timer定時器
Timer定時器是一種一次性定時器,即在未來某個時刻觸發(fā)的事件只會執(zhí)行一次。
Timer的結構中包含一個Time類型的管道C,主要用于事件通知。
在未到達設定時間時,管道內(nèi)沒有數(shù)據(jù)寫入,一直處于阻塞狀態(tài);到達設定時間后,會向管道內(nèi)寫入一個系統(tǒng)時間,觸發(fā)事件。
1. 創(chuàng)建Timer
使用time.NewTimer函數(shù)可以創(chuàng)建一個Timer定時器。該函數(shù)接受一個Duration類型的參數(shù),表示定時器的超時時間,并返回一個*Timer類型的指針。
看下源碼,Timer結構體中,timer.C是一個時間類型的通道
package main import ( "fmt" "time" ) func main() { // func NewTimer(d Duration) *Timer timer := time.NewTimer(2 * time.Second) // 設置超時時間2秒 // 先打印下當前時間 fmt.Println("當前時間:", time.Now()) //我們可以打印下這個只讀通道的值 //timer.C就是我們在定義定時器的時候,存放的時間,等待對應的時間。現(xiàn)在這個就是根據(jù)當前時間加2秒 fmt.Println("通道里面的值", <-timer.C) }
可以看到timer.C這個只讀通道里面的值,就是通過NewTimer設置時間間隔后的時間
因此,我們可以根據(jù)時間通道,來定時將來某個時間要做的事
package main import ( "fmt" "time" ) func main() { timer := time.NewTimer(2 * time.Second) // 設置超時時間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) // 設置超時時間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方法重新激活它,并設置新的超時時間。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方法將定時器重新激活,并設置新的超時時間為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語句確保在程序結束時停止定時器。程序會在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ù)相當于實現(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的結構中也包含一個Time類型的管道C,每隔固定時間段d就會向該管道發(fā)送當前的時間,根據(jù)這個管道消息來觸發(fā)事件。
Ticker定時器是Go標準庫time包中的一個重要組件。它允許你每隔一定的時間間隔執(zhí)行一次指定的操作。Ticker定時器在創(chuàng)建時會啟動一個后臺goroutine,該goroutine會按照指定的時間間隔不斷向一個通道(Channel)發(fā)送當前的時間值。
1. 創(chuàng)建Ticker
要創(chuàng)建一個Ticker定時器,你可以使用time.NewTicker函數(shù)。這個函數(shù)接受一個time.Duration類型的參數(shù),表示時間間隔,并返回一個*time.Ticker類型的指針。
Ticker定時器的核心是一個通道(Channel),你可以通過監(jiān)聽這個通道來接收時間間隔到達的事件。
ticker := time.NewTicker(1 * time.Second)
上面的代碼創(chuàng)建了一個每隔1秒觸發(fā)一次的Ticker定時器。
2. 監(jiān)聽Ticker事件
要監(jiān)聽Ticker定時器的事件,你可以使用range關鍵字或者select語句來監(jiān)聽Ticker定時器的通道。每次時間間隔到達時,Ticker定時器的通道都會接收到一個當前的時間值。
for range ticker.C { // 在這里執(zhí)行周期性任務 }
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ù)結束時停止定時器 for { select { case t := <-ticker.C: fmt.Println("Tick at", t) } } }
每秒執(zhí)行一次
3. 停止Ticker定時器
當你不再需要Ticker定時器時,你應該調(diào)用它的Stop方法來停止它。停止Ticker定時器可以釋放與之關聯(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 { //當達到設置的停止條件式,停止循環(huán) case <-timeEnd: fmt.Println("===結束任務") break case t := <-ticker.C: fmt.Println("==500毫秒響應一次:", t) } } }() time.Sleep(5 * time.Second) // 主線程等待5秒鐘 ticker.Stop() // 停止Ticker定時器 timeEnd <- true // 發(fā)送結束信號 fmt.Println("===定時任務結束===") }
持續(xù)執(zhí)行5秒后,定時器停止運行
三、定時器應用案例
1. 定時打印日志
Ticker定時器的一個常見應用是定時打印日志。通過設置一個Ticker定時器,你可以每隔固定的時間間隔輸出一次日志信息,從而監(jiān)控程序的運行狀態(tài)。
package main import ( "fmt" "time" ) func main() { ticker := time.NewTicker(5 * time.Second) defer ticker.Stop() // 確保程序結束時停止Ticker定時器 for range ticker.C { // 打印當前時間作為日志 fmt.Println("Current time:", time.Now()) } }
5秒打印一次
在這個例子中,我們創(chuàng)建了一個每隔5秒觸發(fā)一次的Ticker定時器,并在一個無限循環(huán)中監(jiān)聽它的事件。
每次事件觸發(fā)時,我們都會打印當前的時間作為日志信息。注意,由于我們在main函數(shù)中使用了defer語句來確保Ticker定時器在程序結束時被停止,所以即使循環(huán)是無限的,程序也不會因為Ticker定時器而泄漏資源。
然而,在實際應用中,你可能需要在某個條件下提前停止Ticker定時器。這時,你可以使用一個額外的通道來發(fā)送停止信號:
package main import ( "fmt" "time" ) func main() { ticker := time.NewTicker(5 * time.Second) stopChan := make(chan struct{}) go func() { // 模擬一個運行一段時間的任務 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來模擬一個運行一段時間的任務,并在任務完成后向stopChan發(fā)送一個停止信號。
在for循環(huán)中,我們使用select語句同時監(jiān)聽Ticker定時器的通道和stopChan通道。當接收到停止信號時,我們停止Ticker定時器并退出程序。
2. 周期性檢查系統(tǒng)狀態(tài)
Ticker定時器還可以用于周期性檢查系統(tǒng)狀態(tài)。例如,你可以每隔一段時間檢查一次服務器的負載、內(nèi)存使用情況或數(shù)據(jù)庫連接數(shù)等關鍵指標,并在發(fā)現(xiàn)異常時采取相應的措施。
package main import ( "fmt" "math/rand" "time" ) // 模擬檢查系統(tǒng)狀態(tài)的函數(shù) func checkSystemStatus() { // 這里可以添加實際的檢查邏輯 // 例如:檢查CPU使用率、內(nèi)存使用情況等 // 這里我們隨機生成一個0到100之間的數(shù)作為模擬結果 status := rand.Intn(101) fmt.Printf("System status: %d\n", status) // 假設狀態(tài)大于80表示系統(tǒng)異常 if status > 80 { fmt.Println("Warning: System status is above normal!") // 這里可以添加處理異常的邏輯 // 例如:發(fā)送警報、重啟服務等 } } func main() { ticker := time.NewTicker(10 * time.Second) defer ticker.Stop() // 確保程序結束時停止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ù)會隨機生成一個0到100之間的數(shù)作為模擬結果,并根據(jù)結果判斷是否系統(tǒng)異常。
如果系統(tǒng)異常(即狀態(tài)大于80),則打印警告信息,并可以在這里添加處理異常的邏輯。
同樣地,你可以使用額外的通道來發(fā)送停止信號,以便在需要時提前停止Ticker定時器。
四、總結
本文詳細介紹了Go語言中Timer和Ticker兩種定時器的用法,并通過實際案例展示了它們的應用場景。Timer定時器適用于需要一次性觸發(fā)的事件,而Ticker定時器適用于需要周期性觸發(fā)的事件
到此這篇關于Go語言中的定時器原理與實戰(zhàn)應用的文章就介紹到這了,更多相關Go語言 定時器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
在Visual Studio Code中配置GO開發(fā)環(huán)境的詳細教程
這篇文章主要介紹了在Visual Studio Code中配置GO開發(fā)環(huán)境的詳細教程,需要的朋友可以參考下2017-02-02Golang中int類型和字符串類型相互轉換的實現(xiàn)方法
在日常開發(fā)中,經(jīng)常需要將數(shù)字轉換為字符串或者將字符串轉換為數(shù)字,在 Golang 中,有一些很簡便的方法可以實現(xiàn)這個功能,接下來就詳細講解一下如何實現(xiàn) int 類型和字符串類型之間的互相轉換,需要的朋友可以參考下2023-09-09