【golang】time.After与time.Ticker与内存泄漏 - Go语言中文社区

【golang】time.After与time.Ticker与内存泄漏


背景

默认的time.After()是会有内存泄漏问题的,因为每次time.After(duratiuon x)会产生NewTimer(), 在duration x到期之前, 新创建的timer不会被GC,到期之后才会GC
那么随着时间推移,尤其是duration x很大的话,会产生内存泄漏的问题。

问题重现

import (
	"fmt"
	"net/http"
	_ "net/http/pprof"
	"time"
)

func Ticker() {
	ch := make(chan string, 100)
	go func() {
		for {
			ch <- "y"
		}
	}()
	go func() {
		if err := http.ListenAndServe("127.0.0.1:9999", nil); err != nil {
			fmt.Println("http server fail")
		}
	}()
	for {
		select {
		case <-ch:
		case <-time.After(time.Minute * 3):
		}
	}
}
go tool pprof -http=:8081 http://localhost:9999/debug/pprof/heap```

在这里插入图片描述
可以看出大量调用time.NewTicker()导致的内存泄漏问题

最佳实践

不用time.After()
而用time.Ticker()

func RightTicker() {
	ch := make(chan string, 100)
	go func() {
		for {
			ch <- "y"
		}
	}()
	go func() {
		if err := http.ListenAndServe("127.0.0.1:9999", nil); err != nil {
			fmt.Println("http server fail")
		}
	}()
	tk := time.NewTimer(time.Minute * 3)
	for {
		select {
		case <-ch:
			if !tk.Stop() {
				<-tk.C
			}
		case <-tk.C:
		}
		tk.Reset(time.Minute * 3)
	}
	tk.Stop()
}
  • timer基础
// Reset changes the timer to expire after duration d.
// It returns true if the timer had been active, false if the timer had
// expired or been stopped.
//
// For a Timer created with NewTimer, Reset should be invoked only on
// stopped or expired timers with drained channels.
//
// If a program has already received a value from t.C, the timer is known
// to have expired and the channel drained, so t.Reset can be used directly.
// If a program has not yet received a value from t.C, however,
// the timer must be stopped and—if Stop reports that the timer expired
// before being stopped—the channel explicitly drained:
//
// 	if !t.Stop() {
// 		<-t.C
// 	}
// 	t.Reset(d)
//
// This should not be done concurrent to other receives from the Timer's
// channel.
//
// Note that it is not possible to use Reset's return value correctly, as there
// is a race condition between draining the channel and the new timer expiring.
// Reset should always be invoked on stopped or expired channels, as described above.
// The return value exists to preserve compatibility with existing programs.
//
// For a Timer created with AfterFunc(d, f), Reset either reschedules
// when f will run, in which case Reset returns true, or schedules f
// to run again, in which case it returns false.
// When Reset returns false, Reset neither waits for the prior f to
// complete before returning nor does it guarantee that the subsequent
// goroutine running f does not run concurrently with the prior
// one. If the caller needs to know whether the prior execution of
// f is completed, it must coordinate with f explicitly.
func (t *Timer) Reset(d Duration) bool {
	if t.r.f == nil {
		panic("time: Reset called on uninitialized Timer")
	}
	w := when(d)
	return resetTimer(&t.r, w)
}
版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/daska110/article/details/119606755
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢