Golang定时器断续器
定时器
1.定时器结构
-
结构定义
type Timer struct { C <-chan Time // 接受定时器事件的通道 r runtimeTimer } type runtimeTimer struct { tb uintptr i int when int64 period int64 f func(interface{}, uintptr) // NOTE: must not be closure arg interface{} seq uintptr }
2.创建定时器
-
接口定义
func NewTimer(d Duration) *Timer
-
使用简单实例
var timer = NewTimer(time.Second) go func() { for { select { case <-timer.C: fmt.Println("time out.") } } }()
-
NewTimer源代码:
func NewTimer(d Duration) *Timer { c := make(chan Time, 1) // 创建一个带有一个Time结构缓冲的通道 t := &Timer{ C: c, r: runtimeTimer{ // 运行时定时器 when: when(d), // 定时多久 f: sendTime, // Golang写入时间的回调接口 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(): default: } } func startTimer(*runtimeTimer)
-
代码实例
package main import ( "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.停止定时器
-
接口定义
func (t *Timer) Stop() bool
-
本接口可以防止计时器出触发。如果定时器停止,则返回true,如果定时器已过期或已停止,则返回false。
Stop不关闭通道,以防止通道读取的操作不正确。 -
为防止通过NewTimer创建的定时器,在调用Stop接口后触发,检查Stop返回值并清空通道。如:
if !t.Stop() { <-t.C }
但不能与Timer的通道中的其他接受同时进行!!!
-
对于使用AfterFunc(d, f)创建的定时器,如果t.Stop()返回false,则定时器已经过期,并且函数f已经在自己的协程中启动。
无论函数f是否执行完成,Stop()返回不会阻塞,如果用户需要知道f是否执行完毕,必须明确地与f协调。
-
-
内部使用接口
func stopTimer(*runtimeTimer) bool
-
代码实例
package main import ( "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.重置定时器
-
接口定义
func (t *Timer) Reset(d Duration) bool
-
定时器被激活,返回true,若定时器已过期或已被停止,返回false。
-
若程序已从t.C中接受数据,定时器已知过期,t.Rest()可直接使用
-
若程序还尚未从t.C收到一个值,则必须停止定时器。如果Stop提示计时器在停止之前已过期,则应明确清空通道。
if !t.Stop() { <-t.c } t.Rest(d)
-
-
代码实例
package main import ( "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 }defer func() { ticker.Stop() fmt.Println("ticker stopped") } () } }(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接口
-
接口定义
func After(d Duration) <-chan Time
-
time.After函数,表示多少时间,写入当前时间,在取出channel时间之前不会阻塞,后续程序可以继续执行
-
time.After函数,通常用于处理程序超时问题
-
等待一段时间d后,Golang会发送当前时间到返回的通道上。
-
底层的定时器不会被GC回收,如果考虑效率,可使用NewTimer创建定时器,如果不需要,则调用Timer.Stop
-
-
源码实例
package main import ( "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接口
-
接口定义
func AfterFunc(d Duration, f func()) *Timer
- 等待一段时间d后,Golang会在自己的协程中调用f。并返回一个定时器,可以使用Stop方法取消调用
-
代码实例
package main import ( "fmt" "time" ) func main() { timer := time.AfterFunc(time.Second*3, func() { fmt.Println("AfterFunc Callback") }) time.Sleep(time.Second * 5) timer.Stop() }
断续器
-
断续器(滴答器)持有一个通道,该通道每隔一段时间发送时钟的滴答
-
注1:从已经关闭的断续器中读取数据发生报错。所以在退出处理断续器流程前,需要先取消断续器。
-
注2:经过取消的断续器,不能再复用,需要重新创建一个新的断续器。
-
结构定义如下:
type Ticker struct { C <-chan Time // The channel on which the ticks are delivered. r runtimeTimer } type runtimeTimer struct { tb uintptr i int when int64 period int64 f func(interface{}, uintptr) // NOTE: must not be closure arg interface{} seq uintptr }
-
初始化断续器
var ticker = time.NewTicker(time.Second)
-
取消断续器
var ticker = time.NewTicker(time.Second) ticker.Stop()
实例一:使用Ticker(并使用时间控制ticker)
-
代码如下:
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
-
代码如下:
package main import ( "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) close(stopChan) }
实例三:使用channel控制停止ticker
-
代码如下:
package main import ( "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) }
-
调试输出:
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 channel Process finished with exit code 0