go-Sleep
引言
time.Sleep(1*time.Second) 这个方法在某些场景很有用,在使用它的时候不由想到。它内部实现原理是什么?如果只是想让出 cpu 时间是不是有更好的方法?执行sleep 后最短回来的时间是多少?
探索分析
首先,我们打开 time.Sleep 源码,出乎意料,一个空的方法,瞬间我迷茫了,难道 go 还有这种使用姿势,定义一个空的方法,然后延迟实现?找了半天不得其法,查完资料才发现在 runtime/time.go 里面有个 timeSleep 方法,有完整的实现。 看注释 //go:linkname timeSleep time.Sleep
,这是一种全新的体验啊,赶紧学习了一下使用姿势。
1 | `//go:` 是编译指令,编译器接受注释形式的指令。 |
time.Sleep 调用流程是 timeSleep→gopark→resetForSleep→resettimer→modtimer→wakeNetPoller
。timeSleep 会创建 timer,并把 goroutineReady 设为其方法。gopark 是很关键的方法,里面主要做了几件事:
- 获取当前 g 的 m,设置 waitlock 相关参数。
- 调用 mcall 来切换协程,会保存当前 g 的 PC/SP ,在 m->g0 堆栈环境里执行 park_m 方法来切换当前 g 的状态并解绑 m,运行 waitlock 的参数即会调用 resetForSleep (里面会将 timer 添加到定时器中)然后清除 ,然后由 schedule 方法来决定下一个 g 调度。
time.Sleep 曾经有个 bug,在 go 1.4 引入抢占调度后,如果在 timer.status 变更成 timerModifying 后,被调度切换走,那么在 gopreempt_m→goschedImpl→schedule→checkTimers->runtimer
里面会无限等待下去。这块在之后加了防止抢占来修复,从防止抢占方法看实际上是个类似读锁的概念。
结论
- go 源码很多切换协程和涉及系统调用的方法都是用汇编写的。
- go time.Sleep 原理简单来说就是创建 timer 然后让出线程,最后靠定时系统协程来唤醒。
- 如果 timeSleep 很短,理论上在 gopark 时就已经过去了,那么它会执行 wakeup。
- 可以用 runtime.Gosched() 来让出 cpu。