简介工作中经常有定时执行某些代码块的需求 , 如果是php代码 , 一般写个脚本 , 然后用Cron实现 。
Go里提供了两种定时器:Timer(到达指定时间触发且只触发一次)和 Ticker(间隔特定时间触发) 。
Timer和Ticker的实现几乎一样 , Ticker相对复杂一些 , 这里主要讲述一下Ticker是如何实现的 。
让我们先来看一下如何使用Ticker
//创建Ticker , 设置多长时间触发一次ticker := time.NewTicker(time.Second * 10)go func() { for range ticker.C { //遍历ticker.C , 如果有值 , 则会执行do someting , 否则阻塞 //do someting }}()
代码很简洁 , 给开发者提供了巨大的便利 。那GoLang是如何实现这个功能的呢?
原理NewTickertime/tick.go的NewTicker函数:
调用NewTicker可以生成Ticker , 关于这个函数有四点需要说明
- NewTicker主要作用之一是初始化
- NewTicker中的时间是以纳秒为单位的 , when返回的从当前时间+d的纳秒值 , d必须为正值
- Ticker结构体中包含channel , sendTime是个function , 逻辑为用select等待c被赋值
- 神秘的startTimer函数 , 揭示channel、sendTime是如何关联的
// NewTicker returns a new Ticker containing a channel that will send the// time with a period specified by the duration argument.// It adjusts the intervals or drops ticks to make up for slow receivers.// The duration d must be greater than zero; if not, NewTicker will panic.// Stop the ticker to release associated resources.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}
time/tick.go的Ticker数据结构// A Ticker holds a channel that delivers `ticks' of a clock// at intervals.type Ticker struct { C <-chan Time // The channel on which the ticks are delivered. r runtimeTimer}
time/sleep.go的runtimeTimer// Interface to timers implemented in package runtime.// Must be in sync with ../runtime/time.go:/^type timertype runtimeTimer struct { tb uintptr i int when int64 period int64 f func(interface{}, uintptr) // NOTE: must not be closure arg interface{} seq uintptr}
time/sleep.go的sendTimefunc sendTime(c interface{}, seq uintptr) { // Non-blocking send of time on c. // Used in NewTimer, it cannot block anyway (buffer). // Used in NewTicker, dropping sends on the floor is // the desired behavior when the reader gets behind, // because the sends are periodic. select { case c.(chan Time) <- Now(): default: }}
time/sleep.go的startTimerfunc startTimer(*runtimeTimer)func stopTimer(*runtimeTimer) bool
startTimer看完上面的代码 , 大家内心是不是能够猜出是怎么实现的?有一个机制保证时间到了时 , sendTime被调用 , 此时channel会被赋值 , 调用ticker.C的位置解除阻塞 , 执行指定的逻辑 。
让我们看一下GoLang是不是这样实现的 。
追踪代码的时候我们发现在time包里的startTimer , 只是一个声明 , 那真正的实现在哪里?
runtime/time.go的startTimer
此处使用go的隐藏技能go:linkname引导编译器将当前(私有)方法或者变量在编译时链接到指定的位置的方法或者变量 。另外timer和runtimeTimer的结构是一致的 , 所以程序运行正常 。
//startTimer将new的timer对象加入timer的堆数据结构中//startTimer adds t to the timer heap.//go:linkname startTimer time.startTimerfunc startTimer(t *timer) { if raceenabled { racerelease(unsafe.Pointer(t)) } addtimer(t)}
runtime/time.go的addtimerfunc addtimer(t *timer) { tb := t.assignBucket() lock(&tb.lock) ok := tb.addtimerLocked(t) unlock(&tb.lock) if !ok { badTimer() }}
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 盘点使用golang作为后台的科技公司
- 彻底搞懂虚拟地址翻译为物理地址的过程
- 一文看懂USB4
- 彻底搞懂虚拟内存,虚拟地址,虚拟地址空间
- 如何彻底搞懂Mysql事务原理
- MySQL高级进阶:关于InnoDB存储结构,一文深入分析讲解
- 帮你彻底搞懂 JS 中的 prototype、__proto__与constructor
- 一文看懂技术中台、研发中台、移动中台建设全攻略
- 一文弄清Python网络爬虫解析库!内含多个实例讲解
- 一文看完CDN的发展历程