time.Sleep(1) 后发生了什么
Time.sleep(d duration)方法会阻塞一个协程的执行直到d时间结束。
实现原理很简单,但内部代码实现却是大有文章,每个go版本的timer的实现都有所不同,本文基于go1.14,接下来分别从宏观和围观介绍一遍主要调度过程。
图文演示
阶段一、进入睡眠
//go:linkname timeSleep time.Sleep
func timeSleep(ns int64) {
if ns <= 0 { //判断入参是否正常
return
}
gp := getg() //获取当前的goroutine
t := gp.timer //如果不存在timer,new一个
if t == nil {
t = new(timer)
gp.timer = t
}
t.f = goroutineReady //后面唤醒时候会用到,修改goroutine状态为goready
t.arg = gp
t.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上,后面会给到p
mp.waitunlockf = unlockf //将函数付给m
......
mcall(park_m) //将当前的g停放
}
func park_m(gp *g) {
_g_ := getg() //获取当前goroutine
......
casgstatus(gp, _Grunning, _Gwaiting) //将goroutine状态设为waiting
dropg()
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,对应timerNoStatus
mp = acquirem()
if atomic.Cas(&t.status, status, timerModifying) {
wasRemoved = true //设置标志位为true
break loop
}
releasem(mp)
badTimer()
}
}
t.period = period
t.f = f //上文传过来的goroutineReady函数,用于将g转变为runnable状态
t.arg = arg //上文的g实例
t.seq = seq
if wasRemoved { //会执行到此处
t.when = when
pp := getg().m.p.ptr() //获取当前的p的指针
lock(&pp.timersLock) //加锁,为了并发安全,因为timer可以去其他的p偷取
doaddtimer(pp, t) //添加定时器到当前的p
unlock(&pp.timersLock) //解锁
if !atomic.Cas(&t.status, timerModifying, timerWaiting) { //转变到timerWaiting
badTimer()
}
......
}
当触发完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 = now
if 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 //就是之前传入的goroutine
seq := t.seq //默认值0
if t.period > 0 {
......... //由于period为默认值0,会走else里面
} else {
dodeltimer0(pp) //删除该计时器在p中,该timer在0坐标位
if !atomic.Cas(&t.status, timerRunning, timerNoStatus) { //设置为nostatus
badTimer()
}
}.......
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/
推荐阅读
评论