Go 定時器:Timer 和 Ticker

前言

在日常開發中,我們可能會遇到需要延遲執行或週期性地執行一些任務。這個時候就需要用到 Go 語言中的定時器。

Go 語言中,定時器類型有兩種:time.Timer 一次性定時器和 time.Ticker 週期性定時器。本文將會對這兩種定時器類型進行介紹。

準備好了嗎?準備一杯你最喜歡的咖啡或茶,隨着本文一探究竟吧。

Timer:一次性定時器

Timer 是一個一次性的定時器,用於在未來的某一時刻執行一次操作。

基本使用

創建 Timer 定時器的方式有兩種:

下面的代碼展示瞭如何使用 NewTimerAfterFunc 來創建定時器以及定時器的基本用法:

// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go/timer/timer/usage.go
package main

import (
	"fmt"
	"time"
)

func main() {
	// 使用 NewTimer 創建一個定時器
	timer := time.NewTimer(time.Second)
	gofunc() {
		select {
		case <-timer.C:
			fmt.Println("timer 定時器觸發啦!")
		}
	}()
	// 使用 AfterFunc 創建另一個定時器
	time.AfterFunc(time.Second, func() {
		fmt.Println("timer2 定時器觸發啦!")
	})
	// 主goroutine等待兩秒,確保看到定時器觸發的輸出
	time.Sleep(time.Second * 2)
}

代碼運行結果如下所示:

timer 定時器觸發啦!
timer2 定時器觸發啦!

下面是代碼的逐步解析:

方法詳解

Reset

Reset(d Duration) bool:該方法用於重置 Timer 定時器的過期時間,也可以理解爲重新激活定時器。它接受一個 time.Duration 類型的參數 d,表示定時器在過期之前等待的時間。

除此之外,該方法還返回一個 bool 值:

下面是代碼示例:

// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go/timer/timer/reset.go
package main

import (
	"fmt"
	"time"
)

func main() {
	timer := time.NewTimer(5 * time.Second)
	// 第一次重置,定時器處於激活狀態,因此返回 true
	b := timer.Reset(1 * time.Second)
	fmt.Println(b) // true

	second := time.Now().Second()
	select {
	case t := <-timer.C:
		fmt.Println(t.Second() - second) // 1s
	}

	// 第二次重置,定時器已經處於過期狀態,因此返回 false
	b = timer.Reset(2 * time.Second)
	fmt.Println(b) // false
	second = time.Now().Second()

	select {
	case t := <-timer.C:
		fmt.Println(t.Second() - second) // 2s
	}
}

代碼運行結果如下所示:

true
1    
false
2

下面是代碼的逐步解析:

Stop

Stop() bool:該方法用於停止定時器。如果定時器停止成功,返回 true,如果定時器已經過期或被停止,則返回 false。切記:Stop 操作不會關閉通道 C

下面是代碼示例:

// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go/timer/timer/stop.go
package main

import (
	"fmt"
	"time"
)

func main() {
	timer := time.NewTimer(3 * time.Second)
	// 停止定時器,在定時器觸發之前停止它,因此返回 true
	stop := timer.Stop()
	fmt.Println(stop) // true

	stop = timer.Stop()
	// 第二次停止定時器,此時定時器已經被停止了,返回 false
	fmt.Println(stop) // false
}

代碼運行結果如下所示:

true
false

下面是代碼的逐步解析:

Ticker:週期性定時器

Tciker 是一個週期性的定時器,用於在固定的時間間隔重複執行任務。它在每個間隔時間到來時,向其通道(Channel)發送當前時間。

基本使用

我們可以使用 NewTicker 函數來創建一個新的 Ticker 對象,該函數接受一個 time.Duration 類型的參數 d(時間間隔)。

下面是代碼示例:

// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go/timer/ticker/usage.go
package main

import (
	"context"
	"fmt"
	"time"
)

func main() {
	ticker := time.NewTicker(time.Second)
	defer ticker.Stop()

	timeout, cancelFunc := context.WithTimeout(context.Background(), time.Second*5)
	defer cancelFunc()

	gofunc() {
		for {
			select {
			case <-timeout.Done():
				fmt.Println("timeout done")
				return
			case <-ticker.C:
				fmt.Println("定時器觸發啦!")
			}
		}
	}()

	// 主goroutine等待 7 秒,確保看到定時器觸發的輸出
	time.Sleep(time.Second * 7)
}

代碼運行結果如下所示:

定時器觸發啦!
定時器觸發啦!
定時器觸發啦!
定時器觸發啦!
定時器觸發啦!
timeout done

下面是代碼的逐步解析:

除了使用 select 語句監聽 ticker.C 以外,我們還可以使用 for range 的形式進行監聽:

for range ticker.C {}

需要注意的是,即使通過 Stop 方法停止 Ticker 定時器,其 C 通道不會被關閉。這意味着無論是通過 for select 還是 for range 去監聽 ticker.C,我們需要使用其他機制來退出循環,例如使用 context 上下文。

方法詳解

Reset

Reset(d Duration) 方法用於停止計時器並將其週期重置爲指定的時間。下一個時間刻度將在新週期結束後生效。它接受一個 time.Duration 類型的參數 d,表示新的週期。該參數必須大於零;否則 Reset 方法內部將會 panic

下面是代碼示例:

// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go/timer/ticker/reset.go
package main

import (
	"time"
)

func main() {
	ticker := time.NewTicker(5 * time.Second)
	defer ticker.Stop()

	// 重置定時器
	ticker.Reset(1 * time.Second)
	second := time.Now().Second()
	for t := range ticker.C {
		// 1s
		fmt.Printf("週期:%d 秒", t.Second()-second)
		break
	}
}

代碼運行結果如下所示:

週期:1 秒

下面是代碼的逐步解析:

Stop

Stop() 方法用於停止定時器。在 Stop 之後,將不再發送更多的 tick 給其通道 C。切記:Stop 操作不會關閉通道 C

下面是代碼示例:

// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go/timer/ticker/stop.go
package main

import (
	"fmt"
	"time"
)

func main() {
	ticker := time.NewTicker(time.Second)
	quit := make(chanstruct{}) // 創建一個退出通道

	gofunc() {
		for {
			select {
			case <-ticker.C:
				fmt.Println("定時器觸發啦!")
			case <-quit:
				fmt.Println("協程停止啦!")
				return// 接收到退出信號,退出循環
			}
		}
	}()

	time.Sleep(time.Second * 3)
	ticker.Stop() // 停止定時器
	close(quit)   // 發送退出信號
	fmt.Println("定時器停止啦!")
}

代碼運行結果如下所示:

定時器觸發啦!
定時器觸發啦!
定時器觸發啦!
協程停止啦!
定時器停止啦!

Stop 不會關閉其通道 C,因此我們需要藉助其他方式(例如退出信號)來清理資源。

Timer 和 Ticker 的主要區別

用途

行爲特點

可控性

結束操作

注意事項

小結

本文深入探討了 Go 語言中的 TimerTicker 定時器,詳細介紹了它們的創建方式、基本用法以及相關的方法等。此外,文章還概括了這兩個定時器之間的主要區別,並強調了在使用過程中的注意事項。

在編寫 Go 代碼時,我們應根據不同的應用場景去選擇合適的定時器。同時,我們應遵循良好的規範,特別是在定時器使用完畢後及時釋放資源,對於避免潛在的內存泄漏問題尤爲重要。

文章裏的所有代碼已上傳至:https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go/timer

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/IgXFofLEwhYYYUtIZ4_rTA