返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

timer

发布于 2024-10-12 19:15:55 字数 2610 浏览 0 评论 0 收藏 0

定时器核心由运行时实现,

// runtime/time.go

type timer struct {
	when   int64
	period int64
    
	f      func(any, uintptr)
	arg    any
}

//go:linkname startTimer time.startTimer
func startTimer(t *timer) {
	addtimer(t)
}

//go:linkname stopTimer time.stopTimer
func stopTimer(t *timer) bool {
	return deltimer(t)
}

从实现上看, NewTimer 创建内核定时器放入 P.timers 堆内。
要么等调度器( schedule )执行后移除,要么主动 Stop 提前删除。

// time/sleep.go

// Interface to timers implemented in package runtime.
type runtimeTimer struct { ... }

type Timer struct {
	C <-chan Time
	r runtimeTimer
}

func NewTimer(d Duration) *Timer {
	c := make(chan Time, 1)
	t := &Timer{
		C: c,
		r: runtimeTimer{
			when: when(d),   // 没有设置 period,一次性。
			f:    sendTime,
			arg:  c,
		},
	}
	startTimer(&t.r)
	return t
}

func (t *Timer) Stop() bool {
	return stopTimer(&t.r)
}

定时器函数 sendTime 不会阻塞。

// sendTime does a non-blocking send of the current time on c.
func sendTime(c any, seq uintptr) {
	select {
	case c.(chan Time) <- Now():
	default:
	}
}

Ticker 设置了 period ,所以定时器不会自动移除,而是留在堆中重复执行。
只能手工调用 Stop 将其从 P.timers 堆里删除。

// time/tick.go

type Ticker struct {
	C <-chan Time // The channel on which the ticks are delivered.
	r runtimeTimer
}

func NewTicker(d Duration) *Ticker {
	c := make(chan Time, 1)
	t := &Ticker{
		C: c,
		r: runtimeTimer{
			when:   when(d),
			period: int64(d),  // 周期性事件,只能主动删除。
			f:      sendTime,  // 周期性执行。
			arg:    c,
		},
	}
	startTimer(&t.r)
	return t
}

func (t *Ticker) Stop() {
	stopTimer(&t.r)
}

显然, Tick 没有返回实例,外部无法控制( Stop )。
所创建定时器便一直留在堆中,孜孜不倦地重复执行。这便是 leak 原因所在。
相比之下, Timer 要好一些,总归在执行后自动移除。

// time/tick.go

func Tick(d Duration) <-chan Time {
	return NewTicker(d).C
}

泄漏模拟

观察 Stop 调用与否,内存变化。

可换成 NewTimerAfter 做同样测试。可注入 net/http/pprof ,在线采样分析。

package main

import (
	"time"
	"runtime"
)

func main() {

	go func() {
		for {
			t := time.NewTicker(time.Second)

			select {
			case <-t.C:
			default:
			}

			// 不用 defer,减少干扰。
			// t.Stop() 

			// 缩短间隔,模拟更多请求。
			time.Sleep(time.Microsecond * 100) 
		}
	}()

	// 延长测试时间。
	for i := 0; i < 30; i++ { 
		time.Sleep(time.Second * 10)
		runtime.GC()
	}
}

/*

$ go build -o test && GODEBUG=gctrace=1 ./test

*/

更多详细内容,请参考《下卷:源码剖析》并发调度 / 其他 / 定时器。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文