golang 定时器

time.After

golang实现计时器:

1.time.After
otherTimeChan = time.After(refreshActiviryInterval * time.Second)
case <-otherTimeChan:
    an.checkAndRefreshAcitivity()
    otherTimeChan = time.After(refreshActiviryInterval * time.Second)

2.time.NewTimer
 timer := time.NewTimer(3 * time.Second)
 <-timer.C

3.time.NewTicker
ticker := time.NewTicker(1 * time.Second)
select {
    case <-ticker.C:
        c.SSEvent("stats", Stats())
}

4.time.AfterFunc
time.AfterFunc(5 * time.Minute, func() {
    fmt.Printf("expired")
})

内部如何实现计时:

其底层结构是runtimeTimer正常情况下,时间到了之后会调用触发函数。NewTimer中创建了一个channel,size = 1。在Timer中则会调用sendTime函数向channel中发消息,用select保证不会堵塞。

func After(d Duration) <-chan Time {
    return NewTimer(d).C
}
//到了指定时间now_t+d调用f(arg, seq)
func NewTimer(d Duration) *Timer {
    c := make(chan Time, 1)
    t := &Timer{
        C: c,
        r: runtimeTimer{
            when: when(d),  //定时时间
            f:    sendTime, //触发函数
            arg:  c,        //触发函数参数
        },
    }
    startTimer(&t.r)
    return t
}
//到时间后调用sendTime函数,向Channel传消息
func sendTime(c interface{}, seq uintptr) {
    select {
    case c.(chan Time) <- Now():
    default:
    }
}

//到了指定时间now_t+d调用f(arg, seq),并计算下一次的时间
func NewTicker(d Duration) *Ticker {
    if d <= 0 {
        panic(errors.New("non-positive interval for NewTicker"))
    }
    c := make(chan Time, 1)
    t := &Ticker{
        C: c,
        r: runtimeTimer{
            when:   when(d),
            period: int64(d),
            f:      sendTime,
            arg:    c,
        },
    }
    startTimer(&t.r)
    return t
}
// 时间d后执行f context包就是借此实现计时
func AfterFunc(d Duration, f func()) *Timer {
    t := &Timer{
        r: runtimeTimer{
            when: when(d),
            f:    goFunc,
            arg:  f,
        },
    }
    startTimer(&t.r)
    return t
}

底层数据结构(多个计时器时谁先触发):

  • 能够快速维护距离当前时间最近的Timer
  • 数量庞大的Timer节点

启动一个单独的goroutine,维护了一个”最小堆”,go timerproc;如果到了触发时间,读取堆顶的timer,执行timer对应的f函数,并移除该timer element。创建一个Timer实则就是在这个最小堆中添加一个element,Stop一个timer,则是从堆中删除对应的element。

  • 没有新增节点:

    计算堆顶节点触发的时间,睡眠到对应的时间。

  • 新增节点:

    如果插入节点经过平衡是堆顶节点,会利用notewakeup(&timers.waitnote)唤醒timerproc

// Timerproc runs the time-driven events.
// It sleeps until the next event in the timers heap.
// If addtimer inserts a new earlier event, it wakes timerproc early.
func timerproc() {
    timers.gp = getg()
    for {
        lock(&timers.lock)
        timers.sleeping = false
        now := nanotime()
        delta := int64(-1)
        for {
            if len(timers.t) == 0 {
                delta = -1
                break
            }
            t := timers.t[0]
            //获取堆顶的节点,计算最快触发事件的时间
            delta = t.when - now
            if delta > 0 {
                break
            }
            if t.period > 0 {
                //计算下一次的触发时间,并维护最小堆
                t.when += t.period * (1 + -delta/t.period)
                siftdownTimer(0)
            } else {
                // 从最小堆中删除
                last := len(timers.t) - 1
                if last > 0 {
                    timers.t[0] = timers.t[last]
                    timers.t[0].i = 0
                }
                timers.t[last] = nil
                timers.t = timers.t[:last]
                if last > 0 {
                    siftdownTimer(0)
                }
                t.i = -1 // mark as removed
            }
            f := t.f
            arg := t.arg
            seq := t.seq
            unlock(&timers.lock)
            if raceenabled {
                raceacquire(unsafe.Pointer(t))
            }
            //调用触发函数 sendTime
            f(arg, seq)
            lock(&timers.lock)
        }
        if delta < 0 || faketime > 0 {
            // 如果最小堆中没有timer
            timers.rescheduling = true
            goparkunlock(&timers.lock, "timer goroutine (idle)", traceEvGoBlock, 1)
            continue
        }
        // 睡眠delta同时监听timers.waitnote
        timers.sleeping = true
        noteclear(&timers.waitnote)
        unlock(&timers.lock)
        notetsleepg(&timers.waitnote, delta)
    }
}

可能遇见的问题:

  • 到时间才释放对应的内存(如果没有主动关闭,退出函数时不会立即释放)
func () {
    select {
        case b := <-c:
            balabala
            return
        case <-timer.C:
            continue
    }
}
  • 频繁的创建销毁

    每次调用NewTime或者After都会重新创建Timer,创建一个新的channel。

Timer.Stop
/*
    Stop prevents the Timer from firing.
    It returns true if the call stops the timer, false if the timer has already
    expired or been stopped.
    Stop does not close the channel
    1.定时器已经到期
    2.定时器未到期
*/
if !t.Stop() {
    <-t.C
}

参考链接

func main() {
    timer := time.NewTimer(3 * time.Second)

    go func() {
        <-timer.C
        fmt.Println("Timer has expired.")
    }()

    timer.Stop()
    time.Sleep(60 * time.Second)
}

执行timer.Stop()之后,只是将节点从最小堆里面删除,等待返回的channel并没有关闭也没有调用sendtime函数。

Timer.Reset

Reset主要是重新设置倒计时时间。

在使用Reset函数时主要考虑Timer.C这个channel中是否是空的,将里面冗余的值取出来(如果能确保没值则不考虑),避免出现逻辑错误。文档中对Reset的使用描述:如果明确timer已经失效,并且t.C已经被取空,那么可以直接使用Reset。

  • 调用stop函数,发现Timer还生效,再调用Reset
  • 调用stop函数,发现Timer已经失效,这时Timer.C中可能有值;需要先判断Timer.C里面的值是否被取出,然后调用Reset
//  if !t.Stop() {
//      利用stop判断是否需要清理channel
//      <-t.C
//  }
//  t.Reset(d)
func (t *Timer) Reset(d Duration) bool {
    if t.r.f == nil {
        panic("time: Reset called on uninitialized Timer")
    }
    w := when(d)
    active := stopTimer(&t.r)
    t.r.when = w
    startTimer(&t.r)
    return active
}
// 每隔xxx秒xxx
func () {
    timer := time.NewTimer(timeoutDuration)
    for {
        case <-timer.C:
            xxxxx
            timer.Reset(timeoutDuration)
    }
}

转载于:https://www.cnblogs.com/Przz/p/10120243.html

你可能感兴趣的:(golang 定时器)