业务中,会有对某段逻辑在未来某一时刻执行或以一定时间间隔周期性执行的需求。golang使用timer及ticker来满足该需求场景。
<br />
<strong>1 Timers</strong>
<br />
Timer表示在未来某一刻执行仅一次的事件。如下代码中,第一个timer表示1s后执行,<-timer.C会一直阻塞,直至预定时间到达。第二个timer表示2s后执行,新启一个goroutine等待时间到达,主routine在时间未到达前即调用了Stop(),这样,新启的goroutine中的逻辑即不会被执行。
<div class="dp-highlighter"><div class="bar"></div><ol start="1" class="dp-j"><li class="alt"><span><span class="keyword">package</span><span> main </span></span></li><li class=""><span> </span></li><li class="alt"><span><span class="keyword">import</span><span> ( </span></span></li><li class=""><span> <span class="string">"fmt"</span><span> </span></span></li><li class="alt"><span> <span class="string">"time"</span><span> </span></span></li><li class=""><span>) </span></li><li class="alt"><span> </span></li><li class=""><span><span class="keyword">func</span> main() { </span></li><li class="alt"><span> timer := time.NewTimer(time.Second) </span></li><li class=""><span> <-timer.C </span></li><li class="alt"><span> fmt.Println(<span class="string">"hello"</span><span>) </span></span></li><li class=""><span> </span></li><li class="alt"><span> timer = time.NewTimer(<span class="number">2</span><span> * time.Second) </span></span></li><li class=""><span> <span class="keyword">go</span> <span class="keyword">func</span>() { </span></li><li class="alt"><span> <-timer.C </span></li><li class=""><span> fmt.Println(<span class="string">"world"</span><span>) </span></span></li><li class="alt"><span> }() </span></li><li class=""><span> <span class="keyword">if</span><span> timer.Stop() { </span></span></li><li class="alt"><span> fmt.Println(<span class="string">"timer stoped"</span><span>) </span></span></li><li class=""><span> } </span></li><li class="alt"><span>} </span></li></ol></div>
time.AfterFunc亦可以创建一个timer,func参数可以是时间到达后自定义的执行函数。
<div class="dp-highlighter"><div class="bar"></div><ol start="1" class="dp-j"><li class="alt"><span><span class="keyword">package</span><span> main </span></span></li><li class=""><span> </span></li><li class="alt"><span><span class="keyword">import</span><span> ( </span></span></li><li class=""><span> <span class="string">"fmt"</span><span> </span></span></li><li class="alt"><span> <span class="string">"time"</span><span> </span></span></li><li class=""><span>) </span></li><li class="alt"><span> </span></li><li class=""><span><span class="keyword">func</span> main() { </span></li><li class="alt"><span> done := <span class="keyword">make</span>(<span class="keyword">chan</span> <span class="keyword">bool</span>) </span></li><li class=""><span> time.AfterFunc(time.Second, <span class="keyword">func</span>() { </span></li><li class="alt"><span> fmt.Println(<span class="string">"hello"</span><span>) </span></span></li><li class=""><span> done <- <span class="keyword">true</span><span> </span></span></li><li class="alt"><span> }) </span></li><li class=""><span> <-done </span></li><li class="alt"><span>} </span></li></ol></div>
一个通常错误的认为是,创建N个timer(无论是以time.NewTimer方式还是以time.AfterFunc方式)会伴随创建出N个goroutine来跟踪对应的指定时间,以在时间到达时执行。如下代码中,开始时,为了可以查询到系统级goroutine堆栈,加了一行代码debug.SetTraceback("system")。不传任何参数会打印创建timer前的堆栈信息,传入一个参数,会打印创建完10000个timer后的堆栈。
创建timer前的goroutine数:
<div class="dp-highlighter nogutter"><div class="bar"></div><ol start="1" class="dp-j"><li class="alt"><span><span>$ go run test.go </span><span class="number">2</span><span>>&</span><span class="number">1</span><span> | grep </span><span class="string">"^goroutine"</span><span> | wc -l </span></span></li><li class=""><span><span class="number">4</span><span> </span></span></li></ol></div>
大量创建timer后的goroutine数:
<div class="dp-highlighter nogutter"><div class="bar"></div><ol start="1" class="dp-j"><li class="alt"><span><span>$ go run test.go hello </span><span class="number">2</span><span>>&</span><span class="number">1</span><span> | grep </span><span class="string">"^goroutine"</span><span> | wc -l </span></span></li><li class=""><span><span class="number">5</span><span> </span></span></li></ol></div>
可以发现创建10000个timer仅创建了一个监听goroutine。这是由于runtime/time.go内部使用堆统一管理timer,新建或停止timer仅是在对堆节点作增删,堆将要执行的timer排序,最近一个节点到达执行时间,即执行,有timer停止即从堆中移除,所以多个timer仅统一使用一个goroutine作调度即可。
<a href="https://github.com/golang/go/blob/master/src/runtime/time.go" target="blank">https://github.com/golang/go/blob/master/src/runtime/time.go</a>
<div class="dp-highlighter"><div class="bar"></div><ol start="1" class="dp-j"><li class="alt"><span><span class="keyword">package</span><span> main </span></span></li><li class=""><span> </span></li><li class="alt"><span><span class="keyword">import</span><span> ( </span></span></li><li class=""><span> <span class="string">"os"</span><span> </span></span></li><li class="alt"><span> <span class="string">"runtime/debug"</span><span> </span></span></li><li class=""><span> <span class="string">"time"</span><span> </span></span></li><li class="alt"><span>) </span></li><li class=""><span> </span></li><li class="alt"><span><span class="keyword">func</span> main() { </span></li><li class=""><span> debug.SetTraceback(<span class="string">"system"</span><span>) </span></span></li><li class="alt"><span> <span class="keyword">if</span><span> len(os.Args) <= </span><span class="number">1</span><span> { </span></span></li><li class=""><span> <span class="keyword">panic</span>(<span class="string">"before"</span><span>) </span></span></li><li class="alt"><span> } </span></li><li class=""><span> <span class="keyword">for</span><span> i := </span><span class="number">0</span><span>; i < </span><span class="number">10000</span><span>; i++ { </span></span></li><li class="alt"><span> time.NewTimer(time.Second) </span></li><li class=""><span> <span class="comment">// time.AfterFunc(time.Second, func() {})</span><span> </span></span></li><li class="alt"><span> } </span></li><li class=""><span> <span class="keyword">panic</span>(<span class="string">"after"</span><span>) </span></span></li><li class="alt"><span>} </span></li></ol></div>
<br />
<strong>2 Tickers</strong>
<br />
Ticker表示一个按一定时间间隔周期性执行的事件。其创建与Timer类似。如下代码中,创建一个每隔1s即触发执行的ticker,新启一个goroutine遍历其时钟chan打印时间,主routine等待5s后停止该ticker,新启的goroutine即不会再收到消息。
<div class="dp-highlighter"><div class="bar"></div><ol start="1" class="dp-j"><li class="alt"><span><span class="keyword">package</span><span> main </span></span></li><li class=""><span> </span></li><li class="alt"><span><span class="keyword">import</span><span> ( </span></span></li><li class=""><span> <span class="string">"fmt"</span><span> </span></span></li><li class="alt"><span> <span class="string">"time"</span><span> </span></span></li><li class=""><span>) </span></li><li class="alt"><span> </span></li><li class=""><span><span class="keyword">func</span> main() { </span></li><li class="alt"><span> ticker := time.NewTicker(time.Second) </span></li><li class=""><span> <span class="keyword">go</span> <span class="keyword">func</span>() { </span></li><li class="alt"><span> <span class="keyword">for</span><span> t := range ticker.C { </span></span></li><li class=""><span> fmt.Println(t) </span></li><li class="alt"><span> } </span></li><li class=""><span> }() </span></li><li class="alt"><span> time.Sleep(<span class="number">5</span><span> * time.Second) </span></span></li><li class=""><span> ticker.Stop() </span></li><li class="alt"><span>} </span></li></ol></div>
<br />
本文代码托管地址:<a href="https://github.com/olzhy/go-excercises/tree/master/timers_and_tickers" target="blank">https://github.com/olzhy/go-excercises/tree/master/timers_and_tickers</a>
原文地址:https://leileiluoluo.com/posts/golang-timers-and-tickers.html
有疑问加站长微信联系(非本文作者))