Go中定时器实现原理及源码解析 (2)

time.After一般是配合select来使用:

func main() { ch1 := make(chan int, 1) select { case e1 := <-ch1: //如果ch1通道成功读取数据,则执行该case处理语句 fmt.Printf("1th case is selected. e1=%v",e1) case <- time.After(2 * time.Second): fmt.Println("Timed out") } }

time.Afterfunc可以在设置时间过后执行一个函数:

func main() { f := func(){ fmt.Printf("Expiration time : %v.\n", time.Now()) } time.AfterFunc(1*time.Second, f) time.Sleep(2 * time.Second) } 分析 初始化&Timer结构体

我们先看看NewTimer方法是如何创建一个Timer的:

type Timer struct { C <-chan Time r runtimeTimer } func NewTimer(d Duration) *Timer { // 初始化一个channel,用于返回 c := make(chan Time, 1) t := &Timer{ C: c, r: runtimeTimer{ when: when(d), f: sendTime, arg: c, }, } // 调用runtime.time的startTimer方法 startTimer(&t.r) return t } func startTimer(*runtimeTimer)

NewTimer方法主要是初始化一个Timer,然后调用startTimer方法,并返回Timer。startTimer方法的真正逻辑并不在time包里面,我们可以使用到上一节提到的使用dlv调试汇编代码:

sleep.go:94 0xd8ea09 e872c7faff call $time.startTimer

得知startTimer实际上调用的是runtime.time.startTimer方法。也就是说time.Timer只是对runtime包中timer的一层wrap。这层自身实现的最核心功能是将底层的超时回调转换为发送channel消息。

下面我们看看runtime.startTimer:

func startTimer(t *timer) { ... addtimer(t) }

startTimer方法会将传入的runtimeTimer转为timer,然后调用addtimer方法。

在NewTimer方法中会初始化一个runtimeTimer结构体,这个结构体实际上会被当做runtime.time中的timer结构体传入到startTimer方法中,所以下面我们来看看timer:

type timer struct { // 对应处理器P的指针 pp puintptr // 定时器被唤醒的时间 when int64 // 唤醒的间隔时间 period int64 // 唤醒时被调用的函数 f func(interface{}, uintptr) // 被调用的函数的参数 arg interface{} seq uintptr // 处于timerModifiedXX状态时用于设置when字段 nextwhen int64 // 定时器的状态 status uint32 }

除此之外,timer还有一些标志位来表示 status 状态:

const ( // 初始化状态 timerNoStatus = iota // 等待被调用 // timer 已在 P 的列表中 timerWaiting // 表示 timer 在运行中 timerRunning // timer 已被删除 timerDeleted // timer 正在被移除 timerRemoving // timer 已被移除,并停止运行 timerRemoved // timer 被修改了 timerModifying // 被修改到了更早的时间 timerModifiedEarlier // 被修改到了更晚的时间 timerModifiedLater // 已经被修改,并且正在被移动 timerMoving ) addtimer 新增 timer

runtime.addtimer

func addtimer(t *timer) { // 定时器被唤醒的时间的时间不能为负数 if t.when < 0 { t.when = maxWhen } // 状态必须为初始化 if t.status != timerNoStatus { throw("addtimer called with initialized timer") } // 设置为等待调度 t.status = timerWaiting when := t.when // 获取当前 P pp := getg().m.p.ptr() lock(&pp.timersLock) // 清理 P 的 timer 列表头中的 timer cleantimers(pp) // 将 timer 加入到 P 的最小堆中 doaddtimer(pp, t) unlock(&pp.timersLock) // 唤醒 netpoller 中休眠的线程 wakeNetPoller(when) }

addtimer 会对 timer 被唤醒的时间 when 进行校验,以及校验 status 必须是新出初始化的 timer;

接着会在加锁后调用 cleantimers 对 P 中对应的 timer 列表的头节点进行清理工作,清理完后调用 doaddtimer 将 timer 加入到 P 的最小堆中,并释放锁;

调用 wakeNetPoller 唤醒 netpoller 中休眠的线程。

下面分别来看看 addtimer 中几个重要函数的具体实现

runtime.cleantimers

func cleantimers(pp *p) { gp := getg() for { // 调度器列表为空,直接返回 if len(pp.timers) == 0 { return } // 如果当前 G 被抢占了,直接返回 if gp.preemptStop { return } // 获取第一个 timer t := pp.timers[0] if t.pp.ptr() != pp { throw("cleantimers: bad p") } switch s := atomic.Load(&t.status); s { case timerDeleted: // 设置 timer 的状态 if !atomic.Cas(&t.status, s, timerRemoving) { continue } // 删除第一个 timer dodeltimer0(pp) // 删除完毕后重置状态为 timerRemoved if !atomic.Cas(&t.status, timerRemoving, timerRemoved) { badTimer() } atomic.Xadd(&pp.deletedTimers, -1) // timer 被修改到了更早或更晚的时间 case timerModifiedEarlier, timerModifiedLater: // 将 timer 状态设置为 timerMoving if !atomic.Cas(&t.status, s, timerMoving) { continue } // 重新设置 when 字段 t.when = t.nextwhen // 在列表中删除后重新加入 dodeltimer0(pp) doaddtimer(pp, t) if s == timerModifiedEarlier { atomic.Xadd(&pp.adjustTimers, -1) } // 设置状态为 timerWaiting if !atomic.Cas(&t.status, timerMoving, timerWaiting) { badTimer() } default: return } } }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpddsg.html