Golang定時器斷續器
定時器
1.定時器結構
結構定義
type Timer struct {
C <-chan Time // 接受定時器事件的通道
r runtimeTimer
}type runtimeTimer struct {
tb uintptr
i intwhen int64 period int64 f func(interface{}, uintptr) // NOTE: must not be closure arg interface{} seq uintptr
}
2.創建定時器
接口定義
func NewTimer(d Duration) *Timer使用簡單實例
```go
var timer = NewTimer(time.Second)go func() {
for {
select {
case <-timer.C:
fmt.Println("time out.")
}
}
}()
```NewTimer源代碼:
```go
func NewTimer(d Duration) *Timer {
c := make(chan Time, 1) // 創建一個帶有一個Time結構緩沖的通道
t := &Timer{
C: c,
r: runtimeTimer{ // 運行時定時器
when: when(d), // 定時多久
arg: c, // 往哪個通道寫入時間
},
}
startTimer(&t.r) // 啟動提交定時器
return t
}// 時間到後,Golang自動調用sendTime接口,嘗試往c通道寫入時間
func sendTime(c interface{}, seq uintptr) {
// 給c通道以非阻塞方式發送時間
// 如果被用於NewTimer, 無論如何不能阻塞.
// 如果被用於NewTicker,接收方未及時接受時間,則會丟棄掉,因為發送時間是周期性的。
select {
case c.(chan Time) <- Now():
}
}func startTimer(*runtimeTimer)
```代碼實例
```go
package mainimport (
"fmt"
"time"
)func main() {
// 創建延遲3s的定時器 exit := make(chan bool) timer := time.NewTimer(3 * time.Second) go func() { defer func() { exit <- true }() select { case <-timer.C: fmt.Println("time out.") return } }() <-exit
}
```
3.停止定時器
- 接口定義
go func (t *Timer) Stop() bool
本接口可以防止計時器出觸發。如果定時器停止,則返回true,如果定時器已過期或已停止,則返回false。
Stop不關閉通道,以防止通道讀取的操作不正確。為防止通過NewTimer創建的定時器,在調用Stop接口後觸發,檢查Stop返回值並清空通道。如:
go if !t.Stop() { <-t.C }
但不能與Timer的通道中的其他接受同時進行!!!對於使用AfterFunc(d, f)創建的定時器,如果t.Stop()返回false,則定時器已經過期,並且函數f已經在自己的協程中啟動。
無論函數f是否執行完成,Stop()返回不會阻塞,如果用戶需要知道f是否執行完畢,必須明確地與f協調。
內部使用接口
go func stopTimer(*runtimeTimer) bool
代碼實例
```go
package mainimport (
"fmt"
"time"
)func main() {
timer := time.NewTimer(time.Second)
time.Sleep(time.Millisecond * 500)
timer.Stop()fmt.Println("timer stopped") time.Sleep(time.Second * 3)
}
```
4.重置定時器
接口定義
go func (t *Timer) Reset(d Duration) bool
定時器被激活,返回true,若定時器已過期或已被停止,返回false。
若程序已從t.C中接受數據,定時器已知過期,t.Rest()可直接使用
- 若程序還尚未從t.C收到一個值,則必須停止定時器。如果Stop提示計時器在停止之前已過期,則應明確清空通道。
go if !t.Stop() { <-t.c } t.Rest(d)
代碼實例
```go
package mainimport (
"fmt"
"time"
)func doTimer(t *time.Timer, exit chan<- bool) {
go func(t *time.Timer) { defer func() { exit <- true }() for { select { case c := <-t.C: fmt.Println("timer timeout at", c) return } } }(t)
}
func main() {
sign := make(chan bool)
timer := time.NewTimer(time.Second * 3)doTimer(timer, sign) time.Sleep(time.Second) // 實際測試:註釋下面三行代碼,效率一樣。 if !timer.Stop() { <-timer.C } timer.Reset(time.Second * 3) fmt.Println("timer reset at", time.Now()) <-sign
}
```
5.After接口
- 接口定義
go func After(d Duration) <-chan Time
- time.After函數,表示多少時間,寫入當前時間,在取出channel時間之前不會阻塞,後續程序可以繼續執行
time.After函數,通常用於處理程序超時問題
- 等待一段時間d後,Golang會發送當前時間到返回的通道上。
底層的定時器不會被GC回收,如果考慮效率,可使用NewTimer創建定時器,如果不需要,則調用Timer.Stop
源碼實例
```
package mainimport (
"fmt"
"time"
)func main() {
sign := make(chan bool)
chan1 := make(chan int)
chan2 := make(chan int)defer func() { close(sign) close(chan1) close(chan2) }() go func() { for { select { case c := <-time.After(time.Second * 3): fmt.Println("After at", c) // 若不往sign通道寫入數據,程序循環每隔3s執行當前case分支。 sign <- true case c1 := <-chan1: fmt.Println("c1", c1) case c2 := <-chan2: fmt.Println("c1", c2) } } }() <-sign
}
```
6.AfterFun接口
接口定義
go func AfterFunc(d Duration, f func()) *Timer
- 等待一段時間d後,Golang會在自己的協程中調用f。並返回一個定時器,可以使用Stop方法取消調用
代碼實例
```go
package mainimport (
"fmt"
"time"
)func main() {
timer := time.AfterFunc(time.Second*3, func() { fmt.Println("AfterFunc Callback") }) time.Sleep(time.Second * 5) timer.Stop()
}
```
斷續器
斷續器(滴答器)持有一個通道,該通道每隔一段時間發送時鐘的滴答
註1:從已經關閉的斷續器中讀取數據發生報錯。所以在退出處理斷續器流程前,需要先取消斷續器。
註2:經過取消的斷續器,不能再復用,需要重新創建一個新的斷續器。
結構定義如下:
```go
type Ticker struct {
C <-chan Time // The channel on which the ticks are delivered.
r runtimeTimer
}type runtimeTimer struct {
tb uintptr
i intwhen int64 period int64 f func(interface{}, uintptr) // NOTE: must not be closure arg interface{} seq uintptr
}
````- 初始化斷續器
go var ticker = time.NewTicker(time.Second)
取消斷續器
go var ticker = time.NewTicker(time.Second) ticker.Stop()
實例一:使用Ticker(並使用時間控制ticker)
代碼如下:
```go
package main
import (
"fmt"
"time"
)func TickerTest() *time.Ticker {
// 創建一個斷續器
var ticker = time.NewTicker(time.Second)go func() { // 使用for + range組合處理斷續器 for t := range ticker.C { fmt.Println("tick at", t) } }() return ticker
}
func main() {
ticker := TickerTest()
time.Sleep(time.Second * 10)
ticker.Stop()
}
```
實例二:使用channel控制ticker
參考鏈接:https://blog.csdn.net/yjp19871013/article/details/82048944
代碼如下:
```go
package mainimport (
"fmt"
"time"
)func DoTicker(ticker *time.Ticker) chan<- bool {
stopChan := make(chan bool)go func(ticker *time.Ticker) { // 註冊停止ticker方法 defer ticker.Stop() for { select { // 處理斷續器事件 case t := <-ticker.C: fmt.Println("tick at", t) // 接受外部停止斷續器事件 case stop := <-stopChan: if stop { fmt.Println("DoTicker Exit") return } } } }(ticker) // 返回由外部控制Ticker停止的Channel return stopChan
}
func main() {
var ticker = time.NewTicker(time.Second) stopChan := DoTicker(ticker) time.Sleep(time.Second * 10) // 停止斷續器 stopChan <- true time.Sleep(time.Second) defer func() { ticker.Stop() fmt.Println("ticker stopped") } () close(stopChan)
}
```
實例三:使用channel控制停止ticker
參考鏈接:https://www.kancloud.cn/digest/batu-go/153534
代碼如下:
```go
package mainimport (
"fmt"
"time"
)func DoTicker(ticker *time.Ticker, times int) {
// 創建有times個緩沖的byte通道
stopChan := make(chan byte, times)go func(ticker *time.Ticker) { defer func() { // 經過調試,defer語句塊並未執行 ticker.Stop() fmt.Println("ticker stopped") } () for t := range ticker.C { fmt.Println("write stop channel") // 寫滿times次後,當前goroutine自動退出 stopChan <- 0 fmt.Println("tick at", t) } // 經調試,該語句並未執行 fmt.Println("DoTicker1 Exit") }(ticker)
}
func main() {
var ticker = time.NewTicker(time.Second)DoTicker(ticker, 5) time.Sleep(time.Second * 10)
}
```調試輸出:
```go
write stop channel
tick at 2019-03-13 11:44:35.932692894 +0800 CST m=+1.000442776
write stop channel
tick at 2019-03-13 11:44:36.932643384 +0800 CST m=+2.000393270
write stop channel
tick at 2019-03-13 11:44:37.932565147 +0800 CST m=+3.000315031
write stop channel
tick at 2019-03-13 11:44:38.932735589 +0800 CST m=+4.000485469
write stop channel
tick at 2019-03-13 11:44:39.932553565 +0800 CST m=+5.000303443
write stop channelProcess finished with exit code 0
```
Golang定時器斷續器