time.Sleep(1) 后发生了什么
Time.sleep(d duration)方法会阻塞一个协程的执行直到d时间结束。
实现原理很简单,但内部代码实现却是大有文章,每个go版本的timer的实现都有所不同,本文基于go1.14,接下来分别从宏观和围观介绍一遍主要调度过程。
图文演示



阶段一、进入睡眠
//go:linkname timeSleep time.Sleepfunc timeSleep(ns int64) {if ns <= 0 { //判断入参是否正常return}gp := getg() //获取当前的goroutinet := gp.timer //如果不存在timer,new一个if t == nil {t = new(timer)gp.timer = t}t.f = goroutineReady //后面唤醒时候会用到,修改goroutine状态为goreadyt.arg = gpt.nextwhen = nanotime() + ns //记录上唤醒时间gopark(resetForSleep, unsafe.Pointer(t), waitReasonSleep, traceEvGoSleep, 1) //调用gopark挂起goroutine}
func gopark(unlockf func(*g, unsafe.Pointer) bool, lock unsafe.Pointer, reason waitReason, traceEv byte, traceskip int) {......省略了大部分代码mp.waitlock = lock //由于runningG和p没有连接,将timer赋值到当前m上,后面会给到pmp.waitunlockf = unlockf //将函数付给m......mcall(park_m) //将当前的g停放}
func park_m(gp *g) {_g_ := getg() //获取当前goroutine......casgstatus(gp, _Grunning, _Gwaiting) //将goroutine状态设为waitingdropg()if fn := _g_.m.waitunlockf; fn != nil { //获取到mresetForSleep函数ok := fn(gp, _g_.m.waitlock) //返回值是true_g_.m.waitunlockf = nil //清空该m的函数空间_g_.m.waitlock = nil //......... }schedule() //触发新的调速循环,可执行队列中获取g到m上进行调度}
func modtimer(t *timer, when, period int64, f func(interface{}, uintptr), arg interface{}, seq uintptr) {......loop:for {switch status = atomic.Load(&t.status); status {......case timerNoStatus, timerRemoved: //由于刚创建,所以timer为默认值0,对应timerNoStatusmp = acquirem()if atomic.Cas(&t.status, status, timerModifying) {wasRemoved = true //设置标志位为truebreak loop}releasem(mp)badTimer()}}t.period = periodt.f = f //上文传过来的goroutineReady函数,用于将g转变为runnable状态t.arg = arg //上文的g实例t.seq = seqif wasRemoved { //会执行到此处t.when = whenpp := getg().m.p.ptr() //获取当前的p的指针lock(&pp.timersLock) //加锁,为了并发安全,因为timer可以去其他的p偷取doaddtimer(pp, t) //添加定时器到当前的punlock(&pp.timersLock) //解锁if !atomic.Cas(&t.status, timerModifying, timerWaiting) { //转变到timerWaitingbadTimer()}......}
当触发完gopark方法,会调用releasem(mp)方法释放当前goroutine与m的连接后,该goroutine脱离当前的m挂起,进入gwaiting状态,不在任何运行队列上。对应上图2。
阶段二、恢复执行
func checkTimers(pp *p, now int64) (rnow, pollUntil int64, ran bool) {......省略掉调整计时器时间的一些步骤lock(&pp.timersLock) //加锁adjusttimers(pp) //调整计时器的时间rnow = nowif len(pp.timers) > 0 {if rnow == 0 {rnow = nanotime()}for len(pp.timers) > 0 {if tw := runtimer(pp, rnow); tw != 0 { //进入runtimer方法,携带系统时间参数与处理器if tw > 0 {pollUntil = tw}break}ran = true}}......}
func runtimer(pp *p, now int64) int64 {for {t := pp.timers[0] //遍历堆顶的定时器.......switch s := atomic.Load(&t.status); s {case timerWaiting: //经过time.Sleep的定时器会是waiting状态if t.when > now { //判断是否超过时间// Not ready to run.return t.when}if !atomic.Cas(&t.status, s, timerRunning) { //修改计时器状态continue}runOneTimer(pp, t, now) //运行该计时器函数return 0 ........
func runOneTimer(pp *p, t *timer, now int64) {........f := t.f //goready函数arg := t.arg //就是之前传入的goroutineseq := t.seq //默认值0if t.period > 0 {......... //由于period为默认值0,会走else里面} else {dodeltimer0(pp) //删除该计时器在p中,该timer在0坐标位if !atomic.Cas(&t.status, timerRunning, timerNoStatus) { //设置为nostatusbadTimer()}}.......unlock(&pp.timersLock)f(arg, seq) //执行goroutineReady方法,唤起等待的goroutine.........}
func goroutineReady(arg interface{}, seq uintptr) {goready(arg.(*g), 0) //该处传入的第二个参数代表调度到运行队列的位置,该处设置为0,说明直接调度到运行队列即将要执行的位置,等待被执行。}
参考文章
【1】《Go计时器》https://draveness.me/golang/docs/part3-runtime/ch06-concurrency/golang-timer/
【2】《Golang定时器底层实现剖析》https://www.cyhone.com/articles/analysis-of-golang-timer/
推荐阅读
评论
