timer 简单来说就是1 个定时器,代表多少秒后执行,当创建1个timer,1秒钟过后,我们就能从timer.C 获取那个时刻的时间,因为系统在那个时刻将当前时间写入到timer.C 了,这时候我们就可以做自己的想做的事了。
package main
import (
"fmt"
"time"
)
func main() {
timer:=time.NewTimer(1*time.Second)
defer timer.Stop()
msg:=<-timer.C
fmt.Println("1秒后打印",msg)
}
//结果:1秒后打印 2021-11-02 22:43:42.2260892 +0800 CST m=+1.016042901
type Timer struct {
C <-chan Time //存储时间的管道
r runtimeTimer //底层存储timer 的堆实现
}
type runtimeTimer struct {
pp uintptr
when int64 //什么时候触发timer
period int64 //如果是周期性任务,执行周期性任务的时间间隔
f func(interface{}, uintptr) // NOTE: must not be closure//到时候执行的回调函数
arg interface{} //执行任务的参数
seq uintptr//回调函数的参数,该参数仅在 netpoll 的应用场景下使用。
nextwhen int64//如果是周期性任务,下次执行任务时间
status uint32//timer 的状态
}
下面只展示跟timer 有关的字段
//Go\src\runtime\runtime2.go +604
type p struct {
.....
//堆顶元素什么时候执行
timer0When uint64
//如果有timer 修改为更早执行时间了,将会将执行时间更新到当更早时间
timerModifiedEarliest uint64
//操作timer 的互斥锁
timersLock mutex
//该p 上的所有timer,必须加锁去操作这个字段,因为不同的p 操作这个字段会有竞争关系
timers []*timer
//p 堆上所有的timer
numTimers uint32
//被标记为删除的timer,要么是我们调用stop,要么是timer 自己触发后过期导致的删除
deletedTimers uint32
....
}
但是与我们常见的,使用二叉树来实现最小堆不同,Golang 这里采用了四叉堆 (4-heap) 来实现。这里 Golang 并没有直接给出解释。 这里直接贴一段 知乎网友对二叉堆和 N 叉堆的分析。
倍。
C 语言知名开源网络库 libev,其timer定时器实现可以在编译时选择采用四叉堆。在它的注释里提到四叉树相比来说缓存更加友好。 根据benchmark,在 50000 + 个 timer 的场景下,四叉树会有 5% 的性能优势。具体可见 libev/ev.c#L2227
//创建一个将会在Duration 时间后将那一刻的时间发生到C 的timer
func NewTimer(d Duration) *Timer {
c := make(chan Time, 1) //创建1个channel
t := &Timer{ //创建一个timer
C: c,
r: runtimeTimer{
when: when(d), //什么时候执行
f: sendTime, //到时候执行的回调函数
arg: c,//执行参数
},
}
startTimer(&t.r) //开始timer
return t
}
sendTime实现
//c interface{} 就是NewTimer 赋值的参数,就是channel
func sendTime(c interface{}, seq uintptr) {
select {
case c.(chan Time) <- Now(): //写不进去的话,C 已满,走default 分支
default:
}
}
和NewTimer 一样 ,只是NewTimer 的语法糖,将创建的timer 的C返回了,这样就可以直接使用了
func After(d Duration) <-chan Time {
return NewTimer(d).C
}
这个函数代表在多少秒后执行传入的回调函数f,并返回timer
func AfterFunc(d Duration, f func()) *Timer {
t := &Timer{
r: runtimeTimer{
when: when(d),//什么时候触发
f: goFunc, //到时候执行的回调函数
arg: f//参数为回调函数
},
}
startTimer(&t.r)
return t
}
//执行的回调函数
func goFunc(arg interface{}, seq uintptr) {
go arg.(func())()
}
func (t *Timer) Stop() bool {
if t.r.f == nil {
panic("time: Stop called on uninitialized Timer")
}
return stopTimer(&t.r) //调用系统的 stopTimer
}
package main
import (
"fmt"
"time"
)
//timer 正常stop
func Test1() {
timer:=time.NewTimer(1*time.Second)
fmt.Println(timer.Stop()) //true
}
//timer 已调用stop
func Test2() {
timer:=time.NewTimer(1*time.Second)
timer.Stop()
fmt.Println(timer.Stop()) //false
}
//timer 已被激活
func Test3() {
timer:=time.NewTimer(1*time.Second)
<-timer.C
fmt.Println(timer.Stop())//false
}
func main() {
Test1()
Test2()
Test3()
}
if !t.Stop() {
<-t.C
}
package main
import (
"time"
)
func main() {
timer:=time.NewTimer(1*time.Second)
for i:=0;i<10;i++{
<-timer.C
}
}
//结果
//fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
D:/code/leetcode/timer.go:10 +0x59
来看看并发的调用stop
package main
import (
"fmt"
"time"
)
func main() {
timer:=time.NewTimer(1*time.Second)
for i:=0;i<100;i++{
go fmt.Println(timer.Stop())
}
}
//虽然没有错误产生,但是也是不可取的,多次执行返回的状态却不一定是正确的.
false
false
false
true //顺序不固定,多次执行结果不一样
false
package main
import (
"fmt"
"time"
)
func main() {
var isComplete bool
timer:=time.AfterFunc(1*time.Second, func() {
time.Sleep(5*time.Second)
fmt.Println("任务完成")
isComplete=true //此例子不是并发操作
})
time.Sleep(2*time.Second) //让timer 过期激活
fmt.Println(timer.Stop(),"isComplete=",isComplete) //true isComplete= false
for {
if isComplete{
break
}
}
fmt.Println("任务完成",isComplete)
}
//下面是执行结果
//false isComplete= false
//任务完成
//任务完成 true
Ticker 形容时钟滴答滴答的声音,在go 中常用来做定时任务,任务到了执行任务。
Ticker 使用案例,常用来做定时任务或者顶层连接心跳,每秒定时做什么
package main
import (
"fmt"
"time"
)
func main() {
t:=time.NewTicker(1*time.Second)
defer t.Stop()
for now:=range t.C{
fmt.Println(now)
}
}
type Ticker struct {
C <-chan Time //chan 定时到了以后,go 系统会忘里面添加一个当前时间的数据
r runtimeTimer
}
func NewTicker(d Duration) *Ticker {
if d <= 0 {
panic(errors.New("non-positive interval for NewTicker"))
}
//这里预留一个缓冲给timer 一样,但是满了以后没人接收后面会丢掉事件
c := make(chan Time, 1)
t := &Ticker{
C: c,
r: runtimeTimer{
when: when(d),
period: int64(d),
f: sendTime, //和ticker 的函数一样
arg: c,
},
}
startTimer(&t.r)
return t
}
调用stopTimer停止ticker,停止不会关闭 channel。channel也不能被并发读
func (t *Ticker) Stop() {
stopTimer(&t.r)
}
调用modTimer修改时间,接下来的激活将在新period后
func (t *Ticker) Reset(d Duration) {
if t.r.f == nil {
panic("time: Reset called on uninitialized Ticker")
}
modTimer(&t.r, when(d), int64(d), t.r.f, t.r.arg, t.r.seq)
}
func Tick(d Duration) <-chan Time {
if d <= 0 {
return nil
}
return NewTicker(d).C
}
func timeSleep(ns int64) {
if ns <= 0 {
return
}
gp := getg()
t := gp.timer
if t == nil {
t = new(timer)
gp.timer = t
}
t.f = goroutineReady
t.arg = gp
t.nextwhen = nanotime() + ns
if t.nextwhen < 0 { // check for overflow.
t.nextwhen = maxWhen
}
gopark(resetForSleep, unsafe.Pointer(t), waitReasonSleep, traceEvGoSleep, 1)
}
func resetForSleep(gp *g, ut unsafe.Pointer) bool {
t := (*timer)(ut)
resettimer(t, t.nextwhen)
return true
}
注意:本文为go 在1.17.2 下面的源码解析,如果是1.15 或者1.16 可能有些不同
来看看下面这些函数,go/src/time 是找不到任何实现的,其实go 官方包一个共性,找不到的都在rumtime里面,我们去runtime 里面找找。
func startTimer(*runtimeTimer)
func stopTimer(*runtimeTimer) bool
func resetTimer(*runtimeTimer, int64) bool
func modTimer(t *runtimeTimer, when, period int64, f func(interface{}, uintptr), arg interface{}, seq uintptr)
在go/src/runtime/time.go,我们发现了这些函数的实现,下面我们来看看源码。
// startTimer adds t to the timer heap.
//go:linkname startTimer time.startTimer
func startTimer(t *timer) {
if raceenabled {
racerelease(unsafe.Pointer(t))
}
addtimer(t)
}
// stopTimer stops a timer.
// It reports whether t was stopped before being run.
//go:linkname stopTimer time.stopTimer
func stopTimer(t *timer) bool {
return deltimer(t)
}
// resetTimer resets an inactive timer, adding it to the heap.
//go:linkname resetTimer time.resetTimer
// Reports whether the timer was modified before it was run.
func resetTimer(t *timer, when int64) bool {
if raceenabled {
racerelease(unsafe.Pointer(t))
}
return resettimer(t, when)
}
// modTimer modifies an existing timer.
//go:linkname modTimer time.modTimer
func modTimer(t *timer, when, period int64, f func(interface{}, uintptr), arg interface{}, seq uintptr) {
modtimer(t, when, period, f, arg, seq)
}
const (
// 默认状态,没有状态
timerNoStatus = iota
// 等待timer 去触发,在P 的堆上
timerWaiting
// 执行 timer function.timer 只会短暂的拥有这个状态
timerRunning
// timer 被删了,应该被从堆里面移除。
// 在这个状态下不要执行,但是任然在P 的堆上。
timerDeleted
// timer 将被移除
// 这个状态也是短暂的
timerRemoving
// timer 已经被移除了,并且不在P 的堆上面
timerRemoved
// The timer 将被修改,这个状态也是短暂的
timerModifying
// timer 被修改成更早的时间了,新的执行时间when 在nextWhen 上面
// timer 在P 的堆上, 可能在错误的地方.
timerModifiedEarlier
// timer 修改成更后面的时间了,新的执行时间when 在nextWhen 上面
// timer 在P 的堆上, 可能在错误的地方.
timerModifiedLater
// timer 被修改并且将被移除。该状态是短暂的
timerMoving
)
官方给的操作时的状态转换
//
// addtimer:
// timerNoStatus -> timerWaiting
// anything else -> panic: invalid value
// deltimer:
// timerWaiting -> timerModifying -> timerDeleted
// timerModifiedEarlier -> timerModifying -> timerDeleted
// timerModifiedLater -> timerModifying -> timerDeleted
// timerNoStatus -> do nothing
// timerDeleted -> do nothing
// timerRemoving -> do nothing
// timerRemoved -> do nothing
// timerRunning -> wait until status changes
// timerMoving -> wait until status changes
// timerModifying -> wait until status changes
// modtimer:
// timerWaiting -> timerModifying -> timerModifiedXX
// timerModifiedXX -> timerModifying -> timerModifiedYY
// timerNoStatus -> timerModifying -> timerWaiting
// timerRemoved -> timerModifying -> timerWaiting
// timerDeleted -> timerModifying -> timerModifiedXX
// timerRunning -> wait until status changes
// timerMoving -> wait until status changes
// timerRemoving -> wait until status changes
// timerModifying -> wait until status changes
// cleantimers (looks in P's timer heap):
// timerDeleted -> timerRemoving -> timerRemoved
// timerModifiedXX -> timerMoving -> timerWaiting
// adjusttimers (looks in P's timer heap):
// timerDeleted -> timerRemoving -> timerRemoved
// timerModifiedXX -> timerMoving -> timerWaiting
// runtimer (looks in P's timer heap):
// timerNoStatus -> panic: uninitialized timer
// timerWaiting -> timerWaiting or
// timerWaiting -> timerRunning -> timerNoStatus or
// timerWaiting -> timerRunning -> timerWaiting
// timerModifying -> wait until status changes
// timerModifiedXX -> timerMoving -> timerWaiting
// timerDeleted -> timerRemoving -> timerRemoved
// timerRunning -> panic: concurrent runtimer calls
// timerRemoved -> panic: inconsistent timer heap
// timerRemoving -> panic: inconsistent timer heap
// timerMoving -> panic: inconsistent timer heap
func siftupTimer(t []*timer, i int) int {
if i >= len(t) {
badTimer()
}
when := t[i].when
if when <= 0 {
badTimer()
}
tmp := t[i]
for i > 0 {
p := (i - 1) / 4 // parent
if when >= t[p].when {
break
}
t[i] = t[p]
i = p
}
if tmp != t[i] {
t[i] = tmp
}
return i
}
func siftdownTimer(t []*timer, i int) {
n := len(t)
if i >= n {
badTimer()
}
when := t[i].when
if when <= 0 {
badTimer()
}
tmp := t[i]
for {
c := i*4 + 1 // left child
c3 := c + 2 // mid child //第三个孩子
if c >= n {
break
}
w := t[c].when //先让w 获取第一个孩子的执行时间
if c+1 < n && t[c+1].when < w { //如果第二个孩子执行时间更小将w 换成第二个孩子
w = t[c+1].when
c++
}
if c3 < n {
w3 := t[c3].when
if c3+1 < n && t[c3+1].when < w3 { //再来比较低三个孩子和第四个孩子
w3 = t[c3+1].when
c3++
}
if w3 < w {//最后选出执行时间最小的孩子
w = w3
c = c3
}
}
if w >= when {
break
}
t[i] = t[c]
i = c
}
if tmp != t[i] {
t[i] = tmp
}
}
在之前我们先聊聊这个函数,禁止抢占
//go:nosplit
func acquirem() *m {
_g_ := getg()
_g_.m.locks++
return _g_.m
}
抢占时机判断
在判断m 是否可以被抢占的时候,有下面几个条件
//C:\Go\src\runtime\preempt.go +287
func canPreemptM(mp *m) bool {
return mp.locks == 0 && mp.mallocing == 0 && mp.preemptoff == "" && mp.p.ptr().status == _Prunning
}
m.locks == 0
)m.mallocing == 0
)m.preemptoff == ""
)p.status == _Prunning
)所以acquirem将m.locks++,禁止抢占,调用mp.locks--,恢复可以抢占
//go:nosplit
func releasem(mp *m) {
_g_ := getg()
mp.locks--
if mp.locks == 0 && _g_.preempt {
// restore the preemption request in case we've cleared it in newstack
_g_.stackguard0 = stackPreempt
}
}
使用:
func addtimer(t *timer) {
// when must be positive. A negative value will cause runtimer to
// overflow during its delta calculation and never expire other runtime
// timers. Zero will cause checkTimers to fail to notice the timer.
if t.when <= 0 {
throw("timer when must be positive")
}
if t.period < 0 {
throw("timer period must be non-negative")
}
if t.status != timerNoStatus {
throw("addtimer called with initialized timer")
}
t.status = timerWaiting
when := t.when
//禁用p被抢占去避免去改变其他p 的堆栈
// Disable preemption while using pp to avoid changing another P's heap.
mp := acquirem()
pp := getg().m.p.ptr() //获取当前p
lock(&pp.timersLock)
cleantimers(pp) //清除timers
doaddtimer(pp, t)//添加timer
unlock(&pp.timersLock)
wakeNetPoller(when)//添加到netpoller
releasem(mp)
}
wakeNetPoller
方法:唤醒网络轮询器中休眠的线程,检查计时器被唤醒的时间(when)是否在当前轮询预期运行的时间(pollerPollUntil)内,若是唤醒。将timer 添加到当前p 的堆上,该函数在锁中执行
func doaddtimer(pp *p, t *timer) {
// Timers 通过netpoll 恢复执行
if netpollInited == 0 { //netpoll 没有初始化,重新初始化netpoll
netpollGenericInit()
}
if t.pp != 0 {
throw("doaddtimer: P already set in timer")
}
t.pp.set(pp) //设置timer 的pp
i := len(pp.timers)
pp.timers = append(pp.timers, t) //添加到timers,也是就是添加到堆尾
siftupTimer(pp.timers, i)//上滤
if t == pp.timers[0] {//如果是堆顶的话,还需要重新更新pp.timer0When
atomic.Store64(&pp.timer0When, uint64(t.when))
}
atomic.Xadd(&pp.numTimers, 1) //将总timer 数量+1
}
使用:可见在netpoll 或者stopTimer 会使用该函数去将timer 状态标记为被删除timer
func deltimer(t *timer) bool {
for {
switch s := atomic.Load(&t.status); s {
case timerWaiting, timerModifiedLater:
// Prevent preemption while the timer is in timerModifying.
// This could lead to a self-deadlock. See #38070.
mp := acquirem()
if atomic.Cas(&t.status, s, timerModifying) {
//必须在改变状态前获取到pp,因为其他goroutine可能 清理掉将timerDeleted timer
tpp := t.pp.ptr()
if !atomic.Cas(&t.status, timerModifying, timerDeleted) {
badTimer()
}
releasem(mp)
atomic.Xadd(&tpp.deletedTimers, 1)
// Timer was not yet run.
return true
} else {
releasem(mp)
}
case timerModifiedEarlier:
// Prevent preemption while the timer is in timerModifying.
// This could lead to a self-deadlock. See #38070.
mp := acquirem()
if atomic.Cas(&t.status, s, timerModifying) {
// Must fetch t.pp before setting status
// to timerDeleted.
tpp := t.pp.ptr()
if !atomic.Cas(&t.status, timerModifying, timerDeleted) {
badTimer()
}
releasem(mp)
atomic.Xadd(&tpp.deletedTimers, 1)
// Timer was not yet run.
return true
} else {
releasem(mp)
}
case timerDeleted, timerRemoving, timerRemoved:
// Timer was already run.
return false
case timerRunning, timerMoving:
// The timer is being run or moved, by a different P.
// Wait for it to complete.
osyield()
case timerNoStatus:
// Removing timer that was never added or
// has already been run. Also see issue 21874.
return false
case timerModifying:
// Simultaneous calls to deltimer and modtimer.
// Wait for the other call to complete.
osyield()
default:
badTimer()
}
}
}
func cleantimers(pp *p) {
gp := getg() //获取执行的gp
for {
if len(pp.timers) == 0 { //如果没有timers 返回
return
}
//从理论上来讲这个循环将会运行一段时间,因为加了timersLock不能被抢占,如果某人想抢占这个goroutine,我们返回等待下一次清理
if gp.preemptStop {
return
}
t := pp.timers[0] //获取堆顶元素
if t.pp.ptr() != pp {
throw("cleantimers: bad p")
}
switch s := atomic.Load(&t.status); s {
case timerDeleted: //被删除了,先将状态改为待移除
if !atomic.Cas(&t.status, s, timerRemoving) {
continue
}
//清除堆顶元素
dodeltimer0(pp)
//将状态改为移除
if !atomic.Cas(&t.status, timerRemoving, timerRemoved) {
badTimer()
}
//将被删除的timers 数量减一
atomic.Xadd(&pp.deletedTimers, -1)
case timerModifiedEarlier, timerModifiedLater:
//时间修改为更早或者更晚,将状态改为移除
if !atomic.Cas(&t.status, s, timerMoving) {
continue
}
// 将执行时间改为下一次移除
t.when = t.nextwhen
dodeltimer0(pp) //先删除堆顶
doaddtimer(pp, t)//重新加入timer
if !atomic.Cas(&t.status, timerMoving, timerWaiting) { //将状态置为等待触发
badTimer()
}
default:
// Head of timers does not need adjustment.
return
}
}
}
真正执行删除
func dodeltimer(pp *p, i int) int {
if t := pp.timers[i]; t.pp.ptr() != pp {
throw("dodeltimer: wrong P")
} else {
t.pp = 0
}//判断timer 的状态
last := len(pp.timers) - 1
if i != last {
pp.timers[i] = pp.timers[last]
}//将timer 和对最后一个timer 交换
pp.timers[last] = nil
pp.timers = pp.timers[:last]
smallestChanged := i
if i != last { //i 等于last ,代表移除最后一个timer.不需要下滤或者上滤
//先上滤,然后下滤,这样保证有序了
smallestChanged = siftupTimer(pp.timers, i)
siftdownTimer(pp.timers, i)
}
if i == 0 { //更改堆顶元素需要,更新pp 上面堆顶元素状态
updateTimer0When(pp)
}
atomic.Xadd(&pp.numTimers, -1) //总数量减一
return smallestChanged
}
删除堆顶的timer
func dodeltimer0(pp *p) {
if t := pp.timers[0]; t.pp.ptr() != pp {
throw("dodeltimer0: wrong P")
} else {
t.pp = 0
}
last := len(pp.timers) - 1
if last > 0 {
pp.timers[0] = pp.timers[last]
}
pp.timers[last] = nil
pp.timers = pp.timers[:last]
if last > 0 {
siftdownTimer(pp.timers, 0)
}
updateTimer0When(pp)
atomic.Xadd(&pp.numTimers, -1)
}
if len(pp.timers) == 0 {
atomic.Store64(&pp.timer0When, 0)
} else {
atomic.Store64(&pp.timer0When, uint64(pp.timers[0].when))
}
该字段将会更新pp 上面堆顶元素的执行时间,因为原来的被删除了,此时应该用一个新的去执行
func modtimer(t *timer, when, period int64, f func(interface{}, uintptr), arg interface{}, seq uintptr) bool {
if when <= 0 { //检验时间
throw("timer when must be positive")
}
if period < 0 {
throw("timer period must be non-negative")
}
status := uint32(timerNoStatus)
wasRemoved := false
var pending bool
var mp *m
loop:
for {
switch status = atomic.Load(&t.status); status {
case timerWaiting, timerModifiedEarlier, timerModifiedLater:
// 禁止p 被抢占
// This could lead to a self-deadlock. See #38070.
mp = acquirem()
//将状态置为待修改
if atomic.Cas(&t.status, status, timerModifying) {
pending = true // timer not yet run
break loop
}
releasem(mp)
case timerNoStatus, timerRemoved:
// 禁止p 被抢占
// This could lead to a self-deadlock. See #38070.
mp = acquirem()
// 禁止p 被抢占
// Act like addtimer.
if atomic.Cas(&t.status, status, timerModifying) {
wasRemoved = true
pending = false // timer already run or stopped
break loop
}
releasem(mp)
case timerDeleted:
// 禁止p 被抢占
// This could lead to a self-deadlock. See #38070.
mp = acquirem()
if atomic.Cas(&t.status, status, timerModifying) {
atomic.Xadd(&t.pp.ptr().deletedTimers, -1)
pending = false // timer 已经运行或者停止了
break loop
}
releasem(mp)
case timerRunning, timerRemoving, timerMoving:
// timer 被当前p 运行或者移除
// 等待状态完成
osyield()
case timerModifying:
// modtimer被多个同时调用
// 等待状态完成
osyield()
default:
badTimer()
}
}
t.period = period
t.f = f
t.arg = arg
t.seq = seq
if wasRemoved { //如果timer 被移除了
t.when = when
pp := getg().m.p.ptr()
lock(&pp.timersLock)
doaddtimer(pp, t) //添加这个timer,注意要加锁,因为不知道哪个p 在操作,会有数据竞争
unlock(&pp.timersLock)
if !atomic.Cas(&t.status, timerModifying, timerWaiting) { //将timer 修改为等待
badTimer()
}
releasem(mp)
wakeNetPoller(when)//加入netpoller 等待唤醒
} else {//如果没有被移除,因为timer 在某些p 的堆上,所以我们不能改变这个字段,如果我们改变了,其他p 的堆将会无序了,所以我们赋值给nextwhen ,让其他p 自己去操作when 字段去重排序堆
t.nextwhen = when
newStatus := uint32(timerModifiedLater) //更新状态,根据修改时间判断时修改为更早了还是更晚了
if when < t.when {
newStatus = timerModifiedEarlier
}
tpp := t.pp.ptr()
if newStatus == timerModifiedEarlier { //修改为更早了,就更新为更早
updateTimerModifiedEarliest(tpp, when)
}
//更新timerModifying状态
if !atomic.Cas(&t.status, timerModifying, newStatus) {
badTimer()
}
releasem(mp)
// If the new status is earlier, wake up the poller.
if newStatus == timerModifiedEarlier {
wakeNetPoller(when) //加入netpoller 等待唤醒
}
}
return pending
}
简单来说就相当于sleep,精度更小,暂停一些,等待其他协程执行
该函数在不同平台的实现不一样
linux(src\runtime\os_linux.go +411))
空实现,简单来说就是不做任何事情
func osyield()
mac(src\runtime\os_darwin.go +347)
//go:nosplit
func osyield() {
usleep(1)
}
//go:nosplit
//高精度的timer 可以用,优先使用高优先级的,不能话就只能使用低优先级的
func usleep(us uint32) {
systemstack(func() {
dt := -10 * int32(us) // relative sleep (negative), 100ns units
// If the high-res timer is available and its handle has been allocated for this m, use it.
// Otherwise fall back to the low-res one, which doesn't need a handle.
if haveHighResTimer && getg().m.highResTimer != 0 {
usleep2HighRes(dt)
} else {
usleep2(dt)
}
})
}
windows(src\runtime\os_darwin.go +1157)
//go:nosplit
func osyield() {
systemstack(switchtothread)
}
// systemstack runs fn on a system stack.
//在系统栈上(g0)调用fn或者在gsignal 栈
//调用并且直接返回
// Otherwise, systemstack is being called from the limited stack
// of an ordinary goroutine. In this case, systemstack switches
// to the per-OS-thread stack, calls fn, and switches back.
// It is common to use a func literal as the argument, in order
// to share inputs and outputs with the code around the call
// to system stack:
//
// ... set up y ...
// systemstack(func() {
// x = bigcall(y)
// })
// ... use x ...
//
//go:noescape
func systemstack(fn func())
switchtothread 说明
// updateTimerModifiedEarliest updates the recorded nextwhen field of the
// earlier timerModifiedEarier value.
// The timers for pp will not be locked.
func updateTimerModifiedEarliest(pp *p, nextwhen int64) {
for {
old := atomic.Load64(&pp.timerModifiedEarliest)
if old != 0 && int64(old) < nextwhen {
return
}
if atomic.Cas64(&pp.timerModifiedEarliest, old, uint64(nextwhen)) {
return
}
}
}
// resettimer resets the time when a timer should fire.
// If used for an inactive timer, the timer will become active.
// This should be called instead of addtimer if the timer value has been,
// or may have been, used previously.
// Reports whether the timer was modified before it was run.
func resettimer(t *timer, when int64) bool {
return modtimer(t, when, t.period, t.f, t.arg, t.seq)
}
调用于C:\Go\src\runtime\proc.go+4926
// moveTimers moves a slice of timers to pp. The slice has been taken
// from a different P.
// This is currently called when the world is stopped, but the caller
// is expected to have locked the timers for pp.
func moveTimers(pp *p, timers []*timer) {
for _, t := range timers { //循环遍历timer
loop:
for {
switch s := atomic.Load(&t.status); s {
case timerWaiting: //如果timer 是将运行的,将timer 状态置为移动
if !atomic.Cas(&t.status, s, timerMoving) {
continue
}
t.pp = 0 //状态改成功后,添加timer 也成功了,将状态重新置为待触发
doaddtimer(pp, t)
if !atomic.Cas(&t.status, timerMoving, timerWaiting) {
badTimer()
}
break loop
case timerModifiedEarlier, timerModifiedLater:
//如果timer 是时间被修改的,将timer 状态置为移动
if !atomic.Cas(&t.status, s, timerMoving) {
continue
}
t.when = t.nextwhen//更新时间
t.pp = 0//状态改成功后,添加timer 也成功了,将状态重新置为待触发
doaddtimer(pp, t)
if !atomic.Cas(&t.status, timerMoving, timerWaiting) {
badTimer()
}
break loop
case timerDeleted: //timer是已经被删除的将timer 状态置为移动
if !atomic.Cas(&t.status, s, timerRemoved) {
continue
}
t.pp = 0 但是pp 置为0
break loop
case timerModifying:
// Loop until the modification is complete.
osyield()
case timerNoStatus, timerRemoved:
badTimer()
case timerRunning, timerRemoving, timerMoving:
// Some other P thinks it owns this timer,
// which should not happen.
badTimer()
default:
badTimer()
}
}
}
}
应用
在p 被销毁时,将会调用这个函数将timer移动到其他p 上面去
func (pp *p) destroy() {
....
if len(pp.timers) > 0 {
plocal := getg().m.p.ptr()
//此时已经进入stw了,但是我们任然需要加锁去避免监控调用timeSleepUntil函数,发生在多个p 的情况下,所以不用担心死锁
lock(&plocal.timersLock)
lock(&pp.timersLock)
moveTimers(plocal, pp.timers) //将销毁的p 的timers 移动到当前执行goroutine 的p 上面
pp.timers = nil
pp.numTimers = 0
pp.deletedTimers = 0
atomic.Store64(&pp.timer0When, 0)
unlock(&pp.timersLock)
unlock(&plocal.timersLock)
}
.....
}
checkTimers 运行 P 准备好的任意一个timer
/usr/local/go/src/runtime/proc.go +3423
func checkTimers(pp *p, now int64) (rnow, pollUntil int64, ran bool) {
//加载第一个timer 什么时候执行,加载第一个timer 是否修改为更早的时间了
next := int64(atomic.Load64(&pp.timer0When))
nextAdj := int64(atomic.Load64(&pp.timerModifiedEarliest))
//如果next=0或者被修改为更早的时间了,则将next 修改为nextAdj
if next == 0 || (nextAdj != 0 && nextAdj < next) {
next = nextAdj
}
if next == 0 {
//没有修改或者重新修正timer
return now, 0, false
}
if now == 0 {
now = nanotime()
}
if now < next {
//当前时间小于下次执行的时间,timer 没有准备去运行,但是我们还是要继续去清理被删除的timers,
//下面的条件决定是否我们需要去清理timer
//timer 的p 不是当前g 运行的p或者删除的timer 数量没有超过总timer 数量的1/4,注意这里是单个p
if pp != getg().m.p.ptr() || int(atomic.Load(&pp.deletedTimers)) <= int(atomic.Load(&pp.numTimers)/4) {
return now, next, false
}
}
lock(&pp.timersLock) //加锁,
if len(pp.timers) > 0 {
adjusttimers(pp, now)
for len(pp.timers) > 0 {
// 注意runtimer 可能会短暂的解锁pp.timersLock.
if tw := runtimer(pp, now); tw != 0 { //进行runtimer 操作,如果tw 大于0,说明没有timer 可以执行,并且堆顶timer的下一次执行时间为tw
if tw > 0 {
pollUntil = tw
}
break
}
ran = true
}
}
//与上面条件相反,满足条件我们进进行清理
if pp == getg().m.p.ptr() && int(atomic.Load(&pp.deletedTimers)) > len(pp.timers)/4 {
clearDeletedTimers(pp)
}
unlock(&pp.timersLock)//解锁
return now, pollUntil, ran
}
func runtimer(pp *p, now int64) int64 {
for {
t := pp.timers[0] //获取第0 个timer,堆顶的timer,根据状态做不同的操作
if t.pp.ptr() != pp {
throw("runtimer: bad p")
}
switch s := atomic.Load(&t.status); s { //原子获取到的timer 的状态
case timerWaiting: //状态代表准备去运行
if t.when > now {//但是t.when 大于当前时间,说明没有准备去运行,直接将下次执行时间返回
// Not ready to run.
return t.when
}
if !atomic.Cas(&t.status, s, timerRunning) { //将状态变为运行中,这个状态是短暂的,continue 原因是因为cas 操作可能失败,所以循环cas 操作
continue
}
runOneTimer(pp, t, now) //运行这个timer,会短暂的接口pp lock
return 0
case timerDeleted: //状态是被删除的,就将状态转为待移除
if !atomic.Cas(&t.status, s, timerRemoving) {
continue
}
dodeltimer0(pp) //删除p第一个timer
if !atomic.Cas(&t.status, timerRemoving, timerRemoved) {
badTimer()
}
atomic.Xadd(&pp.deletedTimers, -1)
if len(pp.timers) == 0 {
return -1
}
case timerModifiedEarlier, timerModifiedLater: //时间被修改成更早或者更晚,这时候需要删除堆顶的timer
if !atomic.Cas(&t.status, s, timerMoving) {
continue
}
t.when = t.nextwhen //将时间更新
dodeltimer0(pp)//删除堆顶的timer,也就是之前的自己
doaddtimer(pp, t)//重新将timer 添加进去
if !atomic.Cas(&t.status, timerMoving, timerWaiting) {//状态改为等待待触发,
badTimer()
}
case timerModifying:
// Wait for modification to complete.
osyield()
case timerNoStatus, timerRemoved: //状态是错误的,不活跃的timer 不会出现在堆上
badTimer()
case timerRunning, timerRemoving, timerMoving:
// These should only be set when timers are locked,
//这些状态只会出现在timer 被locked 时候
badTimer()
default:
badTimer()
}
}
}
func runOneTimer(pp *p, t *timer, now int64) {
if raceenabled {
ppcur := getg().m.p.ptr()
if ppcur.timerRaceCtx == 0 {
ppcur.timerRaceCtx = racegostart(funcPC(runtimer) + sys.PCQuantum)
}
raceacquirectx(ppcur.timerRaceCtx, unsafe.Pointer(t))
}
f := t.f //获取timer执行函数
arg := t.arg//获取timer 参数
seq := t.seq//获取timer 参数
if t.period > 0 { //代表这是周期性任务,时间间隔为t.period
// Leave in heap but adjust next time to fire.
delta := t.when - now
t.when += t.period * (1 + -delta/t.period)
if t.when < 0 { // check for overflow.
t.when = maxWhen
}
siftdownTimer(pp.timers, 0) //堆算法下滤操作,将timer 从timerRunning状态重新置为timerWaiting状态
if !atomic.Cas(&t.status, timerRunning, timerWaiting) {
badTimer()
}
updateTimer0When(pp)
} else {
// 不是周期性任务,删掉timer,然后将timer 的状态改为无状态,等待被回收,不得不说go 官方还是挺仔细的,要是我写返回会被回收,估计就不会去重置timer状态了
dodeltimer0(pp)
if !atomic.Cas(&t.status, timerRunning, timerNoStatus) {
badTimer()
}
}
if raceenabled {
// Temporarily use the current P's racectx for g0.
gp := getg()
if gp.racectx != 0 {
throw("runOneTimer: unexpected racectx")
}
gp.racectx = gp.m.p.ptr().timerRaceCtx
}
unlock(&pp.timersLock)
f(arg, seq)
lock(&pp.timersLock)
if raceenabled {
gp := getg()
gp.racectx = 0
}
}
func adjusttimers(pp *p, now int64) {
// If we haven't yet reached the time of the first timerModifiedEarlier
// timer, don't do anything. This speeds up programs that adjust
// a lot of timers back and forth if the timers rarely expire.
// We'll postpone looking through all the adjusted timers until
// one would actually expire.
first := atomic.Load64(&pp.timerModifiedEarliest)
if first == 0 || int64(first) > now {
if verifyTimers {
verifyTimerHeap(pp)
}
return
}
// We are going to clear all timerModifiedEarlier timers.
atomic.Store64(&pp.timerModifiedEarliest, 0)
var moved []*timer
for i := 0; i < len(pp.timers); i++ { //循环遍历该p 上的所有timer
t := pp.timers[i]
if t.pp.ptr() != pp {
throw("adjusttimers: bad p")
}
switch s := atomic.Load(&t.status); s {
case timerDeleted: //是删除的话,将状态改为将移除
if atomic.Cas(&t.status, s, timerRemoving) {
changed := dodeltimer(pp, i) //删除该位置的timer
if !atomic.Cas(&t.status, timerRemoving, timerRemoved) { //将状态改为已移除
badTimer()
}
atomic.Xadd(&pp.deletedTimers, -1) //已标记删掉的timers -1
// 在for 循环里面删掉了这个timer,此时的i 会变了,获取正确位置的i
i = changed - 1
}
case timerModifiedEarlier, timerModifiedLater:
if atomic.Cas(&t.status, s, timerMoving) {
//将timer 执行时间修改为正确的执行时间
t.when = t.nextwhen
//将timer 从堆里面拿起来,我们不在这里做是因为添加会改变timer 在堆里面位置,在遍历完在重新添加。
changed := dodeltimer(pp, i)
moved = append(moved, t)
//获取正确的i 索引
i = changed - 1
}
case timerNoStatus, timerRunning, timerRemoving, timerRemoved, timerMoving:
badTimer()
case timerWaiting:
// OK, nothing to do.
case timerModifying:
// Check again after modification is complete.
osyield()//等一下
i-- //将i-- ,重复上一步操作
default:
badTimer()
}
}
if len(moved) > 0 {
addAdjustedTimers(pp, moved)
}
if verifyTimers {
verifyTimerHeap(pp)
}
}
//循环的将timer 的重新添加到p 的堆里面去,并将timer 的状态改为正在等待触发
func addAdjustedTimers(pp *p, moved []*timer) {
for _, t := range moved {
doaddtimer(pp, t)
if !atomic.Cas(&t.status, timerMoving, timerWaiting) {
badTimer()
}
}
}
func verifyTimerHeap(pp *p) {
for i, t := range pp.timers { //遍历所有timer
if i == 0 {
// 堆顶timer 直接跳过
continue
}
//这个是4叉堆,所以检查parent的是否大于自己的时间,如果不是panic
// The heap is 4-ary. See siftupTimer and siftdownTimer.
p := (i - 1) / 4
if t.when < pp.timers[p].when {
print("bad timer heap at ", i, ": ", p, ": ", pp.timers[p].when, ", ", i, ": ", t.when, "\n")
throw("bad timer heap")
}
}
if numTimers := int(atomic.Load(&pp.numTimers)); len(pp.timers) != numTimers {
println("timer heap len", len(pp.timers), "!= numTimers", numTimers)
throw("bad timer heap len")
}
}
go/src/runtime/proc.go +3015
作用是:从其他p 偷取可运行的timer 或者goroutine,这里只讨论timer
func stealWork(now int64) (gp *g, inheritTime bool, rnow, pollUntil int64, newWork bool){
................
if stealTimersOrRunNextG && timerpMask.read(enum.position()) {
tnow, w, ran := checkTimers(p2, now)
now = tnow
if w != 0 && (pollUntil == 0 || w < pollUntil) {
pollUntil = w
}
if ran {
if gp, inheritTime := runqget(pp); gp != nil {
return gp, inheritTime, now, pollUntil, ranTimer
}
ranTimer = true
}
}
................
}
该函数作用是寻找可运行的goroutine,会触发timer
go/src/runtime/proc.go +2705
// Finds a runnable goroutine to execute.
// Tries to steal from other P's, get g from local or global queue, poll network.
func findrunnable() (gp *g, inheritTime bool) {
....
now, pollUntil, _ := checkTimers(_p_, 0)
.....
}
一种是在调度循环的时候调用 checkTimers
方法进行计时器的触发
go/src/runtime/proc.go +3291
// One round of scheduler: find a runnable goroutine and execute it.
// Never returns.
func schedule(
.....
top:
pp := _g_.m.p.ptr()
pp.preempt = false
if sched.gcwaiting != 0 {
gcstopm()
goto top
}
if pp.runSafePointFn != 0 {
runSafePointFn()
}
// Sanity check: if we are spinning, the run queue should be empty.
// Check this before calling checkTimers, as that might call
// goready to put a ready goroutine on the local run queue.
if _g_.m.spinning && (pp.runnext != 0 || pp.runqhead != pp.runqtail) {
throw("schedule: spinning with local work")
}
checkTimers(pp, 0)
.....
)
Go runtime 在程序启动的时候会创建一个独立的 M 作为监控线程,叫 sysmon
,这个线程为系统级的 daemon 线程,无需 P 即可运行, sysmon
每 20us~10ms 运行一次。
每次调度器调度和窃取的时候触发,但毕竟是具有一定的随机和不确定性。系统监控触发依然是一个兜底保障,那就是runtime.sysmon 监控线程
func sysmon(){
.....
next, _ := timeSleepUntil()
......
}
返回最近将会执行的timer,这里会遍历所有的p ,获取最先执行的timer 时间并返回
func timeSleepUntil() (int64, *p) {
next := int64(maxWhen)
var pret *p
// Prevent allp slice changes. This is like retake.
lock(&allpLock)
for _, pp := range allp {
if pp == nil {
// This can happen if procresize has grown
// allp but not yet created new Ps.
continue
}
w := int64(atomic.Load64(&pp.timer0When))
if w != 0 && w < next {
next = w
pret = pp
}
w = int64(atomic.Load64(&pp.timerModifiedEarliest))
if w != 0 && w < next {
next = w
pret = pp
}
}
unlock(&allpLock)
return next, pret
}
package main
import (
"fmt"
"time"
)
func main() {
t:=time.NewTimer(1*time.Second)
defer t.Stop()
go func() {
select {
case <-doWork():
fmt.Println("正常退出")
return
case <-t.C:
fmt.Println("超时")
return
}
}()
time.Sleep(5*time.Second)
}
func doWork()chan struct{}{
var ch =make(chan struct{})
go func() {
time.Sleep(2*time.Second) //模拟超时
ch<- struct{}{}
}()
return ch
}
翻车示例,在之前的项目框架里面看到过,幸好timer内存泄漏容易排查,不容易排查的话,上线出现问题可能就会被拖出去祭天了(#`O′)。下面是一个内存泄露的例子,for + Select + After 系列
package main
import (
"log"
"net/http"
_ "net/http/pprof"
"time"
)
func main() {
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
ch := make(chan int, 10)
go func() {
var i = 1
for {
i++
ch <- i
}
}()
for {
select {
case x := <-ch:
println(x)
case <-time.After(3 * time.Minute):
println(time.Now().Unix())
}
}
}
输入调试命令:
如果查看分配内存可以用
go tool pprof -alloc_space http://127.0.0.1:6060/debug/pprof/heap
查看使用内存命令
➜ awesomeProject1 go tool pprof -inuse_space http://127.0.0.1:6060/debug/pprof/heap
Fetching profile over HTTP from http://127.0.0.1:6060/debug/pprof/heap
Saved profile in /Users/wuming/pprof/pprof.alloc_objects.alloc_space.inuse_objects.inuse_space.008.pb.gz
Type: inuse_space
Time: Nov 7, 2021 at 1:34pm (CST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) web //图形界面
(pprof) top //查看内存占用最多项
Showing nodes accounting for 171.92MB, 99.42% of 172.92MB total
Dropped 6 nodes (cum <= 0.86MB)
Showing top 10 nodes out of 14
flat flat% sum% cum cum%
0 0% 0% 169.92MB 98.26% main.main
0 0% 0% 169.92MB 98.26% runtime.main
0 0% 0% 169.92MB 98.26% time.After (inline)
162.01MB 93.69% 93.69% 169.92MB 98.26% time.NewTimer //这个函数占用了大部分内存
7.91MB 4.57% 98.26% 7.91MB 4.57% time.startTimer
2MB 1.16% 99.42% 2MB 1.16% runtime.allocm
0 0% 99.42% 2MB 1.16% runtime.mstart
0 0% 99.42% 2MB 1.16% runtime.mstart0
0 0% 99.42% 2MB 1.16% runtime.mstart1
0 0% 99.42% 2MB 1.16% runtime.newm
(pprof)
图形界面查看示例
原理在前面已经讲过,time.After(3 * time.Minute) 每次select都会执行一次,创建timer,但是没有调用stop 去标记被删除,timer就只有超时以后才会被标记删除,然后被gc 回收,如果超时时间比较长,再加上for 组合,就会一直创建timer,可谓是锅从天上来(#`O′)。
Windows API-SwitchToThread
Go netpoller 网络模型之源码全面解析(二)
难以驾驭的 Go timer,一文带你参透计时器的奥秘
go 专家编程
Golang 定时器底层实现深度剖析