我的编程空间,编程开发者的网络收藏夹
学习永远不晚

GoLang中的timer定时器实现原理分析

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

GoLang中的timer定时器实现原理分析

// NewTimer creates a new Timer that will send
// the current time on its channel after at least duration d.
func NewTimer(d Duration) *Timer {
	c := make(chan Time, 1)
	t := &Timer{
		C: c,
		r: runtimeTimer{
			when: when(d),
			f:    sendTime,
			arg:  c,
		},
	}
	startTimer(&t.r)
	return t
}
// The Timer type represents a single event.
// When the Timer expires, the current time will be sent on C,
// unless the Timer was created by AfterFunc.
// A Timer must be created with NewTimer or AfterFunc.
type Timer struct {
	C <-chan Time
	r runtimeTimer
}
func NewTicker(d Duration) *Ticker {
	if d <= 0 {
		panic(errors.New("non-positive interval for NewTicker"))
	}
	// Give the channel a 1-element time buffer.
	// If the client falls behind while reading, we drop ticks
	// on the floor until the client catches up.
	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
}
type Ticker struct {
	C <-chan Time // The channel on which the ticks are delivered.
	r runtimeTimer
}

ticker 跟 timer 的初始化过程差不多,但是 ticker 比 timer 多了一个 period 参数,意为间隔的意思。

// Interface to timers implemented in package runtime.
// Must be in sync with ../runtime/time.go:/^type timer
type runtimeTimer struct {
	pp       uintptr
	when     int64 //触发时间
	period   int64 //执行周期性任务的时间间隔
	f        func(any, uintptr) // 执行的回调函数,NOTE: must not be closure
	arg      any //执行任务的参数
	seq      uintptr //回调函数的参数,该参数仅在 netpoll 的应用场景下使用
	nextwhen int64 //如果是周期性任务,下次执行任务时间
	status   uint32 //状态
}
// 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:
	}
}

sendTime 采用非阻塞的形式,意为,不管是否存在接收方,此定时器一旦到时间了就要触发掉。

// runtime/runtime2.go
type p struct {
    .....
    // The when field of the first entry on the timer heap.
	// This is updated using atomic functions.
	// This is 0 if the timer heap is empty.
    // 堆顶元素什么时候执行
	timer0When uint64
    // The earliest known nextwhen field of a timer with
	// timerModifiedEarlier status. Because the timer may have been
	// modified again, there need not be any timer with this value.
	// This is updated using atomic functions.
	// This is 0 if there are no timerModifiedEarlier timers.
    // 如果有timer修改为更早执行时间了,将会将执行时间更新到更早时间
	timerModifiedEarliest uint64
    // Lock for timers. We normally access the timers while running
	// on this P, but the scheduler can also do it from a different P.
    // 操作timer的互斥锁
	timersLock mutex
    // Actions to take at some time. This is used to implement the
	// standard library's time package.
	// Must hold timersLock to access.
    //该p 上的所有timer,必须加锁去操作这个字段,因为不同的p 操作这个字段会有竞争关系
	timers []*timer
	// Number of timers in P's heap.
	// Modified using atomic instructions.
    //p 堆上所有的timer数
	numTimers uint32
    // Number of timerDeleted timers in P's heap.
	// Modified using atomic instructions.
    //被标记为删除的timer,要么是我们调用stop,要么是timer 自己触发后过期导致的删除
	deletedTimers uint32
}
// runtime/time.go
type timer struct {
	// If this timer is on a heap, which P's heap it is on.
	// puintptr rather than *p to match uintptr in the versions
	// of this struct defined in other packages.
	pp puintptr
	// Timer wakes up at when, and then at when+period, ... (period > 0 only)
	// each time calling f(arg, now) in the timer goroutine, so f must be
	// a well-behaved function and not block.
	//
	// when must be positive on an active timer.
	when   int64
	period int64
	f      func(any, uintptr)
	arg    any
	seq    uintptr
	// What to set the when field to in timerModifiedXX status.
	nextwhen int64
	// The status field holds one of the values below.
	status uint32
}
// startTimer adds t to the timer heap.
//go:linkname startTimer time.startTimer
func startTimer(t *timer) {
	if raceenabled {
		racerelease(unsafe.Pointer(t))
	}
	addtimer(t)
}
// stopTimer stops a timer.
// It reports whether t was stopped before being run.
//go:linkname stopTimer time.stopTimer
func stopTimer(t *timer) bool {
	return deltimer(t)
}
// addtimer adds a timer to the current P.
// This should only be called with a newly created timer.
// That avoids the risk of changing the when field of a timer in some P's heap,
// which could cause the heap to become unsorted.
func addtimer(t *timer) {
	// when must be positive. A negative value will cause runtimer to
	// overflow during its delta calculation and never expire other runtime
	// timers. Zero will cause checkTimers to fail to notice the timer.
	if t.when <= 0 {
		throw("timer when must be positive")
	}
	if t.period < 0 {
		throw("timer period must be non-negative")
	}
	if t.status != timerNoStatus {
		throw("addtimer called with initialized timer")
	}
	t.status = timerWaiting
	when := t.when
	// Disable preemption while using pp to avoid changing another P's heap.
    // 如果M在此之后被别的P抢占了,那么后续操作的就是别的P上的timers,这是不允许的
	mp := acquirem()
	pp := getg().m.p.ptr()
	lock(&pp.timersLock)
	cleantimers(pp) // 清理掉已经过期的timer,以提高添加和删除timer的效率。
	doaddtimer(pp, t) // 执行添加操作
	unlock(&pp.timersLock)
    // 调用 wakeNetPoller 方法,唤醒网络轮询器,检查计时器被唤醒的时间(when)是
    // 否在当前轮询预期运行的时间(pollerPollUntil)内,若是唤醒。
    // 有的定时器是伴随着网络轮训器的,比如设置的 i/o timeout
    // This can have a spurious wakeup but should never miss a wakeup
    // 宁愿出现错误的唤醒,也不能漏掉一个唤醒
	wakeNetPoller(when)
	releasem(mp)
}
// 将0位置的timer与下面的子节点比较,如果比子节点大则下移。子节点i*4 + 1,i*4 + 2,i*4 + 3,i*4 + 4
siftdownTimer(pp.timers, 0) 
// 将i位置的timer与上面的父节点比较,如果比父节点小则上移。父节点是(i - 1) / 4
siftupTimer(pp.timers, i) 

timer 存储在P中的 timers []*timer成员属性上。timers看起来是一个切片,但是它是按照runtimeTimer.when这个数值排序的小顶堆四叉树,触发时间越早越排在前面。

整体来讲就是父节点一定比其子节点小,子节点之间没有任何关系和大小的要求。

关于acquiremreleasem

//go:nosplit
func acquirem() *m {
	_g_ := getg()
	_g_.m.locks++
	return _g_.m
}
//go:nosplit
func releasem(mp *m) {
	_g_ := getg()
	mp.locks--
	if mp.locks == 0 && _g_.preempt {
		// restore the preemption request in case we've cleared it in newstack
		_g_.stackguard0 = stackPreempt
	}
}

acquirem函数获取当前M,并禁止M被抢占,因为M被抢占时的判断如下

//C:\Go\class="lazy" data-src\runtime\preempt.go +287
func canPreemptM(mp *m) bool {
   return mp.locks == 0 && mp.mallocing == 0 && mp.preemptoff == "" && mp.p.ptr().status == _Prunning
}
  • 运行时没有禁止抢占(m.locks == 0
  • 运行时没有在执行内存分配(m.mallocing == 0
  • 运行时没有关闭抢占机制(m.preemptoff == ""
  • M 与 P 绑定且没有进入系统调用(p.status == _Prunning

timers的触发

// runtime/proc.go
func checkTimers(pp *p, now int64) (rnow, pollUntil int64, ran bool)
// runtime/time.go
func runtimer(pp *p, now int64) int64
func runOneTimer(pp *p, t *timer, now int64)

runtime/time.go文件中提供了checkTimers/runtimer/runOneTimer三个方法。checkTimers方法中,如果当前p的timers长度不为0,就不断地调用runtimers。runtimes会根据堆顶的timer的状态判断其能否执行,如果可以执行就调用runOneTimer实际执行。

触发定时器的途径有两个

  • 通过调度器在调度时进行计时器的触发,findrunnable, schedule, stealWork。
  • 通过系统监控检查并触发计时器(到期未执行),sysmon。

调度器的触发一共分两种情况,一种是在调度循环的时候调用 checkTimers 方法进行计时器的触发。另外一种是当前处理器 P 没有可执行的 Timer,且没有可执行的 G。那么按照调度模型,就会去窃取其他计时器和 G。

即使是通过每次调度器调度和窃取的时候触发,但毕竟是具有一定的随机和不确定性,因此系统监控触发依然是一个兜底保障,在 Go 语言中 runtime.sysmon 方法承担了这一个责任,存在触发计时器的逻辑,在每次进行系统监控时,都会在流程上调用 timeSleepUntil 方法去获取下一个计时器应触发的时间,以及保存该计时器已打开的计时器堆的 P。

在获取完毕后会马上检查当前是否存在 GC,若是正在 STW 则获取调度互斥锁。若发现下一个计时器的触发时间已经过去,则重新调用 timeSleepUntil 获取下一个计时器的时间和相应 P 的地址。检查 sched.sysmonlock 所花费的时间是否超过 50μs。若是,则有可能前面所获取的下一个计时器触发时间已过期,因此重新调用 timeSleepUntil 方法再次获取。如果发现超过 10ms 的时间没有进行 netpoll 网络轮询,则主动调用 netpoll 方法触发轮询。同时如果存在不可抢占的处理器 P,则调用 startm 方法来运行那些应该运行,但没有在运行的计时器。

到此这篇关于GoLang中的timer定时器实现原理分析的文章就介绍到这了,更多相关Go timer定时器内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

GoLang中的timer定时器实现原理分析

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

GoLang中的timer定时器实现原理分析

Timer中对外暴露的只有一个channel,这个channel也是定时器的核心。当计时结束时,Timer会发送值到channel中,外部环境在这个channel收到值的时候,就代表计时器超时了,可与select搭配执行一些超时逻辑
2023-02-02

golang定时器Timer的用法和实现原理解析

这篇文章主要介绍了golang定时器Ticker,本文主要来看一下Timer的用法和实现原理,需要的朋友可以参考以下内容
2023-05-15

golang定时器Timer的用法和实现原理是什么

本篇内容介绍了“golang定时器Timer的用法和实现原理是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!TimerTimer是一种单
2023-07-06

Java多线程定时器Timer原理的示例分析

这篇文章给大家分享的是有关Java多线程定时器Timer原理的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。Timer的schedule(TimeTask task, Date time)的使用该方法的作
2023-05-30

Java定时器Timer的源码分析

通过源码分析,我们可以更深入的了解其底层原理。本文将通过Timer的源码,带大家深入了解Java Timer的使用,感兴趣的小伙伴可以了解一下
2022-11-13

怎么深入Java Timer 定时任务调度器实现原理

这篇文章将为大家详细讲解有关怎么深入Java Timer 定时任务调度器实现原理,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。使用 Java 来调度定时任务时,我们经常会使用 Timer 类
2023-06-02

Golang HTTP服务超时控制实现原理分析

这篇文章主要介绍了Golang HTTP服务超时控制实现原理,HTTP服务的超时控制是保障服务高可用性的重要措施之一,由于HTTP服务可能会遇到网络延迟,资源瓶颈等问题,因此需要对请求进行超时控制,以避免服务雪崩等问题,需要的朋友可以参考下
2023-05-19

Java多线程之定时器Timer的实现

定时/计划功能在Java应用的各个领域都使用得非常多,比方说Web层面。本文主要为大家介绍了Java多线程中定时器Timer的实现,感兴趣的小伙伴可以了解一下
2022-11-13

详解Golang中NewTimer计时器的底层实现原理

本文将主要介绍一下Go语言中的NewTimer,首先展示基于NewTimer创建的定时器来实现超时控制。接着通过一系列问题的跟进,展示了NewTimer的底层实现原理,需要的可以参考一下
2023-05-18

Android定时器Timer的停止和重启实现代码

本文介绍了Android定时器Timer的停止和重启实现代码,分享给大家,具体如下:7月份做了一个项目,利用自定义控件呈现一幅动画,当时使用定时器来控制时间,但是当停止开启时总是出现问题。一直在寻找合理的方法解决这个问题,一直没有找到,最近
2023-05-30

linux定时器实现的原理是什么

Linux定时器的实现原理如下:1. 内核中的定时器是通过“定时器”数据结构来表示的。该数据结构包含了定时器的到期时间、回调函数、回调函数参数等信息。2. 内核中维护了一个全局的定时器链表,用于保存所有的定时器。链表中的定时器按照到期时间的
2023-10-09

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录