golang 源码学习之timer/ticker

源码目录 time/time.go (1.1.4.1)

数据结构

/// time/sleep.go
type Timer struct {
    C <-chan Time  // 时间到达时的通道
    r runtimeTimer
}

/// time/time.go
type Ticker struct {
    C <-chan Time 
    r runtimeTimer
}

/// time/sleep.go
type runtimeTimer struct {
    pp       uintptr
    when     int64  // 到期时间
    period   int64  // ticker的周期。timer只会触发一次,所有为空
    f        func(interface{}, uintptr) // 时间到达时触发的方法
    arg      interface{} 
    seq      uintptr
    nextwhen int64
    status   uint32
}

/// runtime/runtime2.go
type p struct {

    .....

    timersLock mutex

    timers []*timer  // 每个P都有个timer集合。
    
    ....
}

从数据结构上看,timer和ticker其实是一样的。每个P都维护一个timer的最小堆

创建

func NewTicker(d Duration) *Ticker {
    if d <= 0 {  // 时间必须大于0
        panic(errors.New("non-positive interval for NewTicker"))
    }

    c := make(chan Time, 1)  // 通道容量为1,那ticker是否会堵塞呢?
    t := &Ticker{
        C: c,
        r: runtimeTimer{
            when:   when(d), // 到期时间
            period: int64(d), // 周期
            f:      sendTime,  // 时间到达时往通道发送消息
            arg:    c,
        },
    }
    startTimer(&t.r)
    return t
}


// 时间到达时往通道发送消息
func sendTime(c interface{}, seq uintptr) {
    select {
    case c.(chan Time) <- Now():
    default:  // default 说明通道不会堵塞
    }
}


// runtimeTimer的参数比tiker少了个period。因为timer只触发一次
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
}

timer的创建和ticker的创建基本一致,初始化通道和runtimeTimer。

添加timer到P

/// runtime/time.go
func startTimer(t *timer) {
    addtimer(t)
}

func addtimer(t *timer) {

    ....
    
    t.status = timerWaiting
    addInitializedTimer(t)
}

func addInitializedTimer(t *timer) {
    when := t.when

    pp := getg().m.p.ptr()  // 获取当前P
    lock(&pp.timersLock)
    cleantimers(pp) // 清理P中无用的timer
    doaddtimer(pp, t)  // 添加timer到p
    unlock(&pp.timersLock)

    wakeNetPoller(when)
}


func doaddtimer(pp *p, t *timer) {

    ...
    
    t.pp.set(pp)  // timer 和 p关联
    i := len(pp.timers)
    pp.timers = append(pp.timers, t) // timer加入P的timer集合
    siftupTimer(pp.timers, i) // 调整最小堆,最先到达的时间在最前面
    if t == pp.timers[0] { // 如果当前timer就是最小堆的一个,更新最先的到达时间
        atomic.Store64(&pp.timer0When, uint64(t.when))
    }
    atomic.Xadd(&pp.numTimers, 1)  // 更新timer数
}

将timer添加到当前P的最小堆中

调度

/// runtime/proc.go
func schedule() {

    ...
    
    pp := _g_.m.p.ptr()  // 获取当前P
    
    ...
    
    checkTimers(pp, 0)  

}


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 {  // timers集合大于0,循环
            if tw := runtimer(pp, rnow); tw != 0 {  
                if tw > 0 {
                    pollUntil = tw
                }
                break  // checkTimers结束
            }
            ran = true
        }
    }

 
    ...
    
    unlock(&pp.timersLock)

    return rnow, pollUntil, ran
}



func runtimer(pp *p, now int64) int64 {
    for {
        t := pp.timers[0]  // 最小堆的第一个

        switch s := atomic.Load(&t.status); s {
        case timerWaiting:
            if t.when > now { // 时间还没到,返回到期时间,checkTimers的for循环也会终止
                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  // timer的回调方法,创建时候设置的sendTime
    arg := t.arg // 创建时候设置的通道
    seq := t.seq

    if t.period > 0 { // t.period > 0说明是ticker
        delta := t.when - now
        t.when += t.period * (1 + -delta/t.period) // 设置下一轮的到达时间
        siftdownTimer(pp.timers, 0) // 调整最小堆
        if !atomic.Cas(&t.status, timerRunning, timerWaiting) {
            badTimer()
        }
        updateTimer0When(pp) // 更新P的到达时间
    } else { // 如果不是ticker 则移除出最小堆
        dodeltimer0(pp)
        if !atomic.Cas(&t.status, timerRunning, timerNoStatus) {
            badTimer()
        }
    }


    unlock(&pp.timersLock)

    f(arg, seq)  // 执行sendTime

    lock(&pp.timersLock)

}

schedule() 是goroutine调度中的方法(参考我的另一篇博客), 获取当前p的timers最小堆中的一个timer,如果timer时间到期,则执行回调向通道发送消息,通知上层程序。如果是ticker则重新设置下次到达的时间,更新最小堆。

小结

timer和ticker的数据结构相同。每个P都有一个timer的最小堆。在runtime调度时,会获取当前P最小堆的一个timer,如果时间到期,则调用回调方法通过通道通知上层程序。如果timer是ticker则重新设置到期时间。

已离职,求工作

?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,172评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,346评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,788评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,299评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,409评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,467评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,476评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,262评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,699评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,994评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,167评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,499评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,149评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,387评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,028评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,055评论 2 352