Go 高階: 定時器的使用
Timer
Timer 是一種單一事件定時器,就是說 Timer 只執行一次就會結束。
創建:
time.NewTimer(d Duration)
:創建一個 timer
源碼包 src/time/sleep.go:Timer 定義了 Timer 數據結構:
type Timer struct {
C <-chan Time
r runtimeTimer
}
Timer 對外僅暴露了一個 channel,當指定時間到來就會往該 channel 中寫入系統時間,即一個事件。
timer 使用
設定超時時間
比如在一個連接中等待數據,設定一個超時時間,當時間到來還是沒有數據獲取到,則爲超時。
func WaitChannel(conn <-chan string) bool {
timer := time.NewTimer(3 * time.Second)
select {
case <- conn:
timer.Stop()
return true
case <- timer.C: //超時
fmt.Println("WaitChannel timeout")
return false
}
}
如上示例中,select 語句輪詢 conn 和 timer.C 兩個管道,timer 會在 3s 後向 timer.C 寫入數據,如果 3s 內 conn 還沒有數據,則會判斷爲超時。
延遲執行方法
有時我們希望某個方法在今後的某個時刻執行:
func DelayFunction() {
timer := time.NewTimer(5 * time.Second)
select {
case <- time.C
fmt.Println("Delayed 5s,...")
}
}
DelayFunction() 會一直等待 timer 的事件到來纔會執行後面的方法 (打印)。
Timer 對外接口
創建定時器
func NewTimer(d Duration) *Timer
:
-
指定一個時間即可創建一個 Timer,Timer 一經創建便開始計時,不需要額外的啓動。
-
創建 Timer 意味着把一個計時任務交給系統守護協程,該協程管理着所有的 Timer,當 Timer 的時間到達後向 Timer 的管道中發送當前的時間作爲事件。
停止定時器
func (t *Timer) Stop() bool
:
-
Timer 創建後可隨時停止
-
返回值表示是否超時:
-
true : 定時器未超時,後續不會再有事件發送
-
false : 定時器超時後停止
重置定時器
func (t *Timer) Reset(d Duration) bool
:
-
已經過期的定時器或已經停止的定時器,可以通過重置來重新激活
-
重置的動作實質上是先停掉定時器,再啓動。其返回值也即停掉計時器的返回值。
簡單接口
After()
有時我們就是想等指定的時間,沒有需求提前停止定時器,也沒有需求複用該定時器,那麼可以使用匿名的定時器:
func AfterDemo(){
log.Println(time.Now)
<- time.After(1 * time.Second)
log.Println(time.Now())
}
打印時間間隔爲 1s,實際還是一個定時器,但代碼變得更簡潔。
AfterFunc()
我們可以使用 AfterFunc 更加簡潔的實現延遲一個方法的調用:
func AfterFunc(d Duration, f func()) *Timer
示例:
func AfterFuncDemo() {
log.Println("AfterFuncDemo start", time.Now())
time.AfterFunc(1 * time.Second, func(){
log.Println("AfterFuncDemo end", time.Now())
})
time.Sleep(2 * time.Second) //等待協程退出
}
-
AfterFuncDemo() 中先打印一個時間,然後使用 AfterFunc 啓動一個定時器,並指定定時器結束時執行一個方法打印結束時間。
-
time.AfterFunc() 是異步執行的,所以需要在函數最後 sleep 等待指定的協程退出,否則可能函數結束時協程還未執行。
Ticker
Ticker 是週期性定時器,即週期性的觸發一個事件。其數據結構和 Timer 完全一致:
type Timer struct {
C <-chan Time
r runtimeTimer
}
在創建 Ticker 時會指定一個時間,作爲事件觸發的週期。這也是 Ticker 與 Timer 的最主要的區別。
Ticker 使用
定時任務
示例,每隔 1s 記錄一次日誌:
func TickerDemo() {
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for range ticker.C {
log.Println("ticker...")
}
}
for range ticker.C
會持續從管道中獲取事件,收到事件後打印一行日誌,如果管道中沒有數據會阻塞等待事件,由於 ticker 會週期性的向管道中寫入事件,所以上述程序會週期性的打印日誌。
Ticker 對外接口
創建定時器
func NewTicker(d Durtion) * Ticker
:
- 參數
d
爲定時器事件觸發的週期。
停止定時器
func (t * Ticker) Stop()
:
-
該方法會停止計時,意味着不會向定時器的管道中寫入事件,但管道並不會被關閉。管道在使用完成後,生命週期結束後會自動釋放。
-
Ticker 在使用完後務必要釋放,否則會產生資源泄露,進而會持續消耗 CPU 資源,最後會把 CPU 耗盡。
簡單接口
如果我們需要一個定時輪詢任務,可以使用一個簡單的 Tick 函數來獲取定時器的管道,函數原型如下:
func TIck(d Durtion) <-chan Time
:
- 這個函數內部實際還是創建一個 Ticker,但並不會返回出來,所以沒有手段來停止該 Ticker。所以,一定要考慮具體的使用場景。
錯誤示例
func WorngTicker() {
for {
select {
case <- time.Tick(1 * time.Second)
log.Println("資源泄露")
}
}
}
如上錯誤示例,select 每次檢測 case 語句時都會創建一個定時器,for 循環又會不斷的執行 select 語句,所以系統裏會有越來越多的定時器不斷的消耗 CPU 資源,最終 CPU 會被耗盡。
正確用法:
func demo(t interface{}) {
for {
select {
case <-t.(*time.Ticker).C:
println("1s timer")
}
}
}
func main() {
t := time.NewTicker(time.Second * 1)
go demo(t)
select {}
}
圖片及部分相關技術知識點來源於網絡搜索,侵權刪!
參考資料:
https://book.douban.com/subject/35144587/
https://www.cnblogs.com/codingnote/archive/2020/03/12/12469098.html
https://blog.csdn.net/x356982611/article/details/80425030
NEW////ARRIVAL
微信公衆號
gophpython
我的微信
wucs_dd
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/er2ABNeKXjI5MGFdZ9FgMw