Go中的定时器Timer

前言

  • 在go1.14 版本中,首先把存放定时事件的四叉堆放到p结构中,使用netpoll的epoll wait来做就近时间的休眠等待。在每次runtime.schedule调度时都检查运行到期的定时器。

大概使用

有时候我们会在开发中会使用到time.NewTicker或者time.NewTimer进行定时或者延时的处理,两者的底层实现基本是一样的,我们可以先来看看Timer的大概使用方式

import (
    "fmt"
    "time"
)

func main() {
 timer := time.NewTimer(2 * time.Seconds)
 <-timer.C
 fmt.Println("延时2s打印")
}

Timer的底层实现

我们先关注一下time.NewTimer,他在time/sleep.go里面
在这里插入图片描述
大概过程就是创建一个Timer对象,调用startTimer启动timer

type Timer struct {
	C <-chan Time
	r runtimeTimer
}

type runtimeTimer struct {
	pp       uintptr
	when     int64 // 定时器被唤醒的时间
	period   int64 // 两次被唤醒的讲个
	f        func(interface{}, uintptr) // 被唤醒之后调用的函数
	arg      interface{}
	seq      uintptr
	nextwhen int64
	status   uint32
}
// 当定时器失效时,失效的时间就会被发送给当前定时器持有的 Channel C,订阅管道中消息的 Goroutine 就会收到当前定时器失效的时间。
  • 另一个用于创建 Timer 的方法 AfterFunc 其实也提供了非常相似的结构,与 NewTimer 方法不同的是该方法没有创建一个用于通知触发时间的 Channel,它只会在定时器到期时调用传入的方法
func AfterFunc(d Duration, f func()) *Timer {
    t := &Timer{
        r: runtimeTimer{
            when: when(d),
            f:    goFunc,
            arg:  f,
        },
    }
    startTimer(&t.r)
    return t
}
startTimer

通过link做方法映射,time/sleep.go里调用的time.startTimer其实是runtime包里的。

func startTimer(t *timer) {
    addtimer(t)
}
addtimer
// 把定时任务放到当前g关联的P里。
func addtimer(t *timer) {
    if t.when < 0 {
        t.when = maxWhen
    }
    t.status = timerWaiting  // 状态为等待中

    addInitializedTimer(t)
}

// ------------------
// 加锁来清理任务,并且增加定时任务,最后根据时间就近来唤醒netpoll
func addInitializedTimer(t *timer) {
    when := t.when

    pp := getg().m.p.ptr()
    lock(&pp.timersLock)
    ok := cleantimers(pp) && doaddtimer(pp, t)
    //cleantimers(pp)就是删除第一个定时任务,其实ticker其实就是在回调函数中再把这个任务塞回去。
    unlock(&pp.timersLock)
    if !ok {
        badTimer()
    }

    wakeNetPoller(when)
}

//
//当新添加的定时任务when小于netpoll等待的时间,那么wakeNetPoller会激活NetPoll的等待。
// 激活的方法很简单,在findrunnable里的最后会使用超时阻塞的方法调用epollwait,这样既可监控了epfd红黑树上的fd,又可兼顾最近的定时任务的等待。
// 唤醒正在netpoll休眠的线程,前提是when的值小于pollUntil时间。
func wakeNetPoller(when int64) {
    if atomic.Load64(&sched.lastpoll) == 0 {
        pollerPollUntil := int64(atomic.Load64(&sched.pollUntil))
        if pollerPollUntil == 0 || pollerPollUntil > when {
            netpollBreak()
        }
    }
}

sleep 的实现

我们通常使用 time.Sleep(1 * time.Second)来将goroutine暂时休眠一段时间。sleep 操作在底层实现也是基于timer 实现的。代码在runtime/time.go

  • 如果完全用定时器来代替Sleep的话会损耗性能
timer := time.NewTimer(2 * time.Seconds)
<-timer.C
  1. 每次调用 sleep 的时候,都要创建一个 timer 对象。
  2. 需要一个 channel 来传递事件。

所以go用另一种方式来做:

  • 每个goroutine底层的 G 对象上,都有一个 timer 属性,这是个 runtimeTimer 对象,专门给 sleep使用。当第一次调用 sleep的时候,会创建这个 runtimeTimer,之后 sleep的时候会一直复用这个timer对象。
  • 调用 sleep时候,触发timer后,直接调用gopark,将当前 goroutine挂起。
  • 调用 callback的时候,直接调 goready 唤醒被挂起的 goroutine
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值