日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

Go語言實現定時器的原理及使用詳解_Golang

作者:yuzhang_zy ? 更新時間: 2023-01-20 編程語言

0. 前言

在進行并發編程時,有時候會需要定時功能,比如監控某個GO程是否會運行過長時間、定時打印日志等等。

GO標準庫中的定時器主要有兩種,一種為Timer定時器,一種為Ticker定時器。Timer計時器使用一次后,就失效了,需要Reset()才能再次生效。而Ticker計時器會一直生效,接下來分別對兩種進行介紹。

1. Timer定時器

首先介紹一下GO定時器的實現原理。

在一個GO進程中,其中的所有計時器都是由一個運行著 timerproc() 函數的 goroutine 來保護。它使用時間堆(最小堆)的算法來保護所有的 Timer,其底層的數據結構基于數組的最小堆,堆頂的元素是間隔超時最近的 Timer,這個 goroutine 會定期 wake up,讀取堆頂的 Timer,執行對應的 f 函數或者 sendtime()函數(下文會對這兩個函數進行介紹),而后將其從堆頂移除。

接著看看Timer的結構:

type Timer struct {
    C <-chan Time
    // contains filtered or unexported fields
}

Timer中對外暴露的只有一個channel,這個 channel 也是定時器的核心。當計時結束時,Timer會發送值到channel中,外部環境在這個 channel 收到值的時候,就代表計時器超時了,可與select搭配執行一些超時邏輯。可以通過time.NewTimer、time.AfterFunc或者 time.Afte對一個Timer進行創建。

1.1 time.NewTimer() 和 time.After()

1.1.1 time.NewTimer()

查看以下簡單的應用代碼:

package main

import (
   "fmt"
   "time"
)
type H struct {
   t *time.Timer
}

func main()  {
   fmt.Println("main")
   h:=H{t: time.NewTimer(1*time.Second)}
   go h.timer()
   time.Sleep(10*time.Second)
}

func (h *H) timer()  {
   for  {
      select {
      case <-h.t.C:
         fmt.Println("timer")
      }
   }

}

我們創建了一個timer,設置時間為1S。然后使用一個select對timer的C進行接受,GO程運行timer()函數,會一直阻塞直到超時發生(接收到C的數據),此時打印timer。

Stop() 停止 Timer

func (t *Timer) Stop() bool

Stop() 是 Timer 的一個方法,調用 Stop()方法,會停止這個 Timer 的計時,使其失效,之后觸發定時事件。

實際上,調用此方法后,此Timer會被從時間堆中移除。

Reset()重置Timer

注意,Timer定時器超時一次后就不會再次運行,所以需要調用Reset函數進行重置。修改select中代碼,在Case中添加一個重置的代碼:

select {
case <-h.t.C:
   fmt.Println("timer")
   h.t.Reset(1*time.Second)
}

可以看到,會不停的打印timer,這是因為使用了Reset函數重置定時器。

注意!不能隨意的對Reset方法進行調用,官網文檔中特意強調:

For a Timer created with NewTimer, Reset should be invoked only on stopped or expired timers with drained channels.

大概的意思就是說,除非Timer已經被停止或者超時了,否則不要調用Reset方法,因為,如果這個 Timer 還沒超時,不先去Stop它,而是直接Reset,那么舊的 Timer 仍然存在,并且仍可能會觸發,會產生一些意料之外的事。所以通常使用如下的代碼,安全的重置一個不知狀態的Timer(以上的代碼中,Reset調用時,總是處于超時狀態):

if !t.Stop() {
    select {
    case <-h.t.C: 
    default:
    }
}
h.t.Reset(1*time.Second)

1.1.2 time.After()

此方法就像是一個極簡版的Timer使用,調用time.After(),會直接返回一個channel,當超時后,此channel會接受到一個值,簡單使用如下:

package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Println("main")
	go ticker()
	time.Sleep(100 * time.Second)
}

func ticker() {
	for {
		select {
		case <-time.After(1 * time.Second):
			fmt.Println("timer")
		}
	}
}

注意,此方法雖然簡單,但是沒有Reset方法來重置定時器,但是可以搭配for 和select的重復調用來模擬重置。

1.1.3 sendtime函數

NewTimer和After這兩種創建方法,會Timer在超時后,執行一個標準庫中內置的函數:sendTime,來將當前的時間發送到channel中。

1.2 time.AfterFunc

此方法可以接受一個func類型參數,在計時結束后,會運行此函數,查看以下代碼,猜猜會出現什么結果?

package main

import (
   "fmt"
   "time"
)

func main() {
   fmt.Println("main")
   t := time.AfterFunc(1*time.Second, func() {
      fmt.Println("timer")
   })
   go timer(t)
   time.Sleep(10 * time.Second)
}
func timer(t *time.Timer) {
   select {
   case <-t.C:
      fmt.Println("123")
   }
}

結果只打印了main以及timer。這是因為此方法并不會調用上文提到的sendtime()函數,即不會發送值給Timer的Channel,所以select就會一直阻塞。

f函數

特意將AfterFunc和以上的NewTimer和After,就是因為f函數的存在。這種方式創建的Timer,在到達超時時間后會在單獨的goroutine里執行函數f,而不會執行sendtime函數。

注意,外部傳入的f參數并非直接運行在timerproc中,而是啟動了一個新的goroutine去執行此方法。

2. Ticker定時器

Ticker定時器可以周期性地不斷地觸發時間事件,不需要額外的Reset操作。

其使用方法與Timer大同小異。通過time.NewTicker對Ticker進行創建,簡單的使用如下:

package main

import (
   "fmt"
   "time"
)

func main() {
   fmt.Println("main")
   t:=time.NewTicker(1*time.Second)
   go timer(t)
   time.Sleep(10 * time.Second)
}
func timer(t *time.Ticker) {
   for{
      select {
      case <-t.C:
         fmt.Println("timer")
      }
   }
}

原文鏈接:https://blog.csdn.net/qq_39445165/article/details/124285999

欄目分類
最近更新