Go Timer 的使用姿势

- Go

之前写 Raft 的时候,用 Timer 来处理定时事件,但是之后在测试的时候遇到了一些诡异的问题,具体表现是随着测试重复进行,CPU 占用率越来越高。上 pprof 检查了一下,发现存在 Timer 泄露,根源是自己 Timer 的使用有些问题。下文记录正确的 Go 中 Timer 的使用姿势。

使用场景:

例子:

错误用法:

正确用法:for 外用 time.NewTicker 创建一个 Ticker,defer close,然后在 select 内,如果有事件则 reset。

示例代码:

package main

import (
	"fmt"
	"log"
	//"math/rand"
	"net/http"
	"time"
	_ "net/http/pprof"
)

func main() {
	fmt.Println("hello world")

	go func() {
		log.Println(http.ListenAndServe("0.0.0.0:6060", nil))
	}()


	c := make(chan bool)

	// consumer
	// correct
	go func() {
		heartbeatInterval := 5 * time.Millisecond
		heartbeatTicker := time.NewTicker(heartbeatInterval)
		defer heartbeatTicker.Stop()
		for {
			select {
			case val := <- c:
				fmt.Println("received ",val, " at " , time.Now())
				heartbeatTicker.Reset(heartbeatInterval)
				if !val{
					return
				}
			case <- heartbeatTicker.C:
				fmt.Println("heartbeat: ", time.Now())
			}
		}
	}()


	// faulty, will cause heartbeak leak
	//go func() {
	//	for {
	//		heartbeat := time.Tick(5 * time.Millisecond)
	//		select {
	//		case val := <- c:
	//			fmt.Println("received ",val, " at " , time.Now())
	//		case <- heartbeat:
	//			//fmt.Println("heartbeat: ", time.Now())
	//		}
	//	}
	//}()

	// producer
	go func() {
		time.Sleep(1500 * time.Millisecond)
		c <- true
		time.Sleep(50 * time.Millisecond)
		c <- true
		time.Sleep(300 * time.Millisecond)
		c <- true
		time.Sleep(900 * time.Millisecond)
		c <- true
		time.Sleep(5 * time.Second)
        
        // stop the simulation
		c <- false
		//rng := rand.New(rand.NewSource(time.Now().Unix()))
		//for {
		//	time.Sleep(time.Duration(rng.Intn(5000)) * time.Millisecond)
		//	c <- true
		//}
		//fmt.Println("PRODUCER DONE")
	}()

	// keep main alive
	select {

	}


}
# run pprof
go tool pprof -http=0.0.0.0:8090  "http://localhost:6060/debug/pprof/profile?seconds=10"

如果使用上文代码中标记 faulty 的版本,在 pprof 的输出中可以发现 epollwaitsendTime 占用了大量的 CPU 时间。在 Raft 作业的测试中,每个测试样例都会开启一个新的 Raft Run,但是不会重启 Runtime,导致之前样例中泄露的 Timer 在整个测试过程中会一直存活,CPU 占用率不断上升。

Leak