本篇内容介绍了“Golang中NewTimer计时器的底层实现原理是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
首先展示基于NewTimer
创建的定时器来实现超时控制。接着通过一系列问题的跟进,展示了NewTimer
的底层实现原理。
我们首先通过一个简单的例子,来展示是怎么基于NewTimer
实现超时控制的。
假设有一个需求,要求在 5 秒钟内完成某个任务,否则就认为任务失败。这时我们就可以使用 NewTimer
来实现超时控制。具体的实现步骤如下:
首先,基于NewTimer
创建一个 Timer 对象,设定超时时间为 5 秒钟。
然后,启动一个 goroutine 来执行任务,并在任务完成后向通道发送一个完成信号。
最后,使用 select 语句等待 Timer 的到期事件或任务完成信号,如果 Timer 先到期,就认为任务超时了,否则就认为任务完成了。
下面是一个简单的实现代码展示:
package main import ( "fmt" "time" ) func main() { // 创建一个定时器,超时时间为 5 秒钟 timer := time.NewTimer(5 * time.Second) // 启动一个 goroutine 执行任务,并在任务完成后向通道发送一个完成信号 done := make(chan bool, 1) go func() { // 模拟任务执行耗时 time.Sleep(2 * time.Second) done <- true }() // 等待任务完成或者超时 select { case <-done: // 任务完成,输出完成信息 fmt.Println("Task finished.") case <-timer.C: // 超时,输出超时信息 fmt.Println("Task timed out.") } }
在上述代码中,我们首先使用NewTimer
创建了一个time.Timer
对象,超时时间为 5 秒钟。然后启动一个 goroutine 来执行任务,并在任务完成后向通道 done
发送一个完成信号。最后使用 select
语句等待任务完成信号或者Timer
的到期事件,如果Timer
先到期,就认为任务超时了,否则就认为任务完成了。 在运行上述代码时,我们可以看到,在任务完成前 5 秒钟内,程序输出如下信息:
Task finished.
如果将任务完成时间改为超过 5 秒钟,程序将会在 5 秒钟后超时,输出如下信息:
Task timed out.
通过这个简单的例子,我们可以看到,如果任务在指定超时时间内完成,此时会执行正常的业务逻辑;如果任务未在指定的超时时间内完成,此时将走执行超时逻辑。
通过上述程序演示,我们展示了如何使用NewTimer
创建的 time.Timer
实现超时控制的基本方法。
回顾上面的示例代码,我们实现超时控制的主要机制,是通过select
语句同时监听两个channel
,一个是任务执行状态的channel
,一个是定时器的channel
。
当任务执行完成时,便通过channel
对主协程进行通知。当定时器到达我们指定的时间,也就是超时时间,此时也通过定时器的channel
进行通知。
同时监听这两个channel
,如果任务先执行完成,此时将会走select
语句中正常业务逻辑case
的代码,如果是在到达预定时间,任务仍没有完成,此时通过定时器channel
进行通知,从而走超时业务逻辑case
的代码,从而实现超时控制。
因此,这里主要的问题是,是如何在到达超时时间时,准时往定时器中的C
对应的channel
发送送数据,从而来告知其他协程,已经到达超时时间了呢,这个是如何做到的呢?
下面先来看看NewTimer
方法返回Timer
结构体的内容,定义如下:
type Timer struct { C <-chan Time r runtimeTimer }
可以看到,Timer
结构体中C是一个chan Time
类型的变量。在前面的代码的例子中,select
语句是监听Timer
结构体中的C
变量,从中来读取数据的。
那么,当到达超时时间时,Timer
中C
对应的channel
将会有数据到达,那么肯定有其他地方,在到达超时时间时,会往Timer
中的C
发送数据。
那么现在的主要问题,是怎么做到当到达指定时间时,往Timer
中的C
发送数据呢?
其实,在go
语言中,存在这样一个运行时函数startTimer
,定义如下:
func startTimer(*runtimeTimer)
它的作用是启动一个定时器,当定时器到期时,会执行相应的回调函数并传递回调参数。在 startTimer
函数内部,会使用系统调用来启动一个底层的操作系统定时器,等到定时器超时时,底层系统会自动触发一个信号(例如 Unix 平台上的 SIGALRM 信号),然后该信号将由 Go 运行时内部的信号处理函数捕获,并最终调用相关的回调函数。
那么,这里我们似乎可以使用startTimer
来实现,当到达指定时间时,往channel
发送数据,从而达到通知其他协程的效果。
首先,我们已经知道,startTimer
能够启动一个定时器,当定时器到期时,会执行相应的回调函数并传递回调参数。而定时器的到期时间、回调函数以及回调函数的参数,则是通过runtimeTimer
结构体传递过去的。
下面我们只需要runtimeTimer
字段的含义,然后根据其含义,正确设置runtimeTimer
结构体字段,调用startTimer
方法启动一个定时器,就能够实现在指定时间时,执行某段逻辑。下面我们来看看runtimeTime
的定义:
type runtimeTimer struct { pp uintptr when int64 period int64 f func(any, uintptr) // NOTE: must not be closure arg any seq uintptr nextwhen int64 status uint32 }
下面对runtimeTimer
中的字段进行说明:
when int64
:表示定时器应该在何时触发。该值的单位是纳秒
period int64
:表示定时器的重复周期。如果定时器不需要重复触发,则该值为 0
f func(any, uintptr)
:指向定时器到期后需要执行的函数。
arg any
:表示传递给定时器到期后执行的函数的参数,将传递给f
的第一个参数
nextwhen int64
:如果定时器是重复触发的,则 nextwhen
表示下一次触发的时间。
seq uintptr
:用于防止定时器被错误的重置。当定时器被重置时,seq
会被更新。
基于对上面字段含义的理解,此时我们定义一个runtimeTimer
结构体,然后调用startTimer
,从而来实现能够在指定的某个时间点,往某个channel
发送数据。具体实现如下:
// 定义一个channel,用于发送数据 c := make(chan Time, 1) r := runtimeTimer{ // 指定超时时间戳 when: when(d), // 指定回调函数 f: func sendTime(c any, seq uintptr) { select { case c.(chan Time) <- Now(): default: } }, // 传递给回调函数的参数 arg: c, }, } // 调用startTimer启动一个定时器 startTimer(&t.r)
首先会创建一个带有缓冲的通道 c
。
接着初始化runtimeTimer
结构体的值,设定好超时时间,回调函数以及参数。超时时间使用的是when
函数来获取计数器的结束时间。when
函数会根据给定的时间间隔 d,返回一个绝对时间点,即计时器结束时间。
f
字段指定的回调函数,则是将当前时间 Now()
发送到通道 c
中。当到达指定超时时间时,其将会调用回调函数f
,同时将runtimeTimer
结构体中arg
字段的值,作为参数传递到回调函数当中。
然后调用startTimer
启动一个定时器。当到达超时时间,将会调用回调函数,回调函数其会往一开始定义的channel
发送数据。
至此,我们实现了最开始提到的,当到达指定时间时,往Timer
中的C
发送数据这个任务。
回到我们的题目,NewTimer计时器的底层实现原理是什么?事实上,NewTimer
创建的定时器,也确实是基于startTimer
来实现的,下面我们来看看其实现:
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 }
首先会创建一个带有缓冲的通道 c
。然后创建一个 Timer
对象 t
,将 c
通道赋值给 t
的 C
属性。channel
之后将作为回调函数的参数,同时也会作为Timer
对象中C
属性的值。这样子回调函数和Timer
结构体中C
变量与回调函数的channel
事实上是共用一个channel
的。
而runtimeTimer
结构体中的回调函数sendTime
的实现与之前讲述的并无差异,都是将当前时间 Now()
发送到通道中,这里将不再赘述。
而Timer
结构体中C
变量与回调函数的channel
事实上是共用一个channel
的,当到达超时时间,则会执行回调函数,往channel
发送数据。而通过select
语句对Timer
中的channel
进行监听的协程,此时也正常接收到通知了。
“Golang中NewTimer计时器的底层实现原理是什么”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。