当前位置: 首页 > 工具软件 > ticker > 使用案例 >

Go专家编程 timer、ticker

闻人树
2023-12-01

golang中timer和ticker 都属于time包

timer是一次性定时器,执行完一次就结束了,ticker是周期性定时器,周而复始的执行。二者在数据结构上完全一样,都是一个对外的channel + 对内的runtimeTimer。

type Timer struct {
    C <-chan Time
    r runtimeTimer
}
type Ticker struct {
    C <-chan Time // The channel on which the ticks are delivered.
    r runtimeTimer
}

一:Timer常用的使用方式:

1:timer = time.NewTimer(1 * time.Second) timer.Stop() timer.Reset()

2: <-time.After(1 * time.Second)

3:time.AfterFunc(1 * time.Second,callback)

二:Ticker常用的使用方式:

1:ticker = time.NewTicker(1 * time.Second) ticker.Stop() for range ticker.C

2:<-time.Tick(1 * time.Second)

三:runtimeTimer结构

type runtimeTimer struct {
    tb *timersBucket                    // 存储当前定时器的数组地址
    i  int                              // 存储当前定时器的数组下标
    when   int64                        // 当前定时器触发时间
    period int64                        // 当前定时器周期触发间隔
    f      func(interface{}, uintptr)   // 定时器触发时执行的函数
    arg    interface{}                  // 定时器触发时执行函数传递的参数一
    seq    uintptr                      // 定时器触发时执行函数传递的参数二(该参数只在网络收发场景下使用)
}

timer和ticker 唯一的区别就是在period上,为timer时其值恒为0,为ticker时其值就是循环周期。

四:实现原理

func NewTimer(d Duration) *Timer {
    c := make(chan Time, 1)  // 创建一个管道
    t := &Timer{ // 构造Timer数据结构
        C: c,               // 新创建的管道
        r: runtimeTimer{
            when: when(d),  // 触发时间
            f:    sendTime, // 触发后执行函数sendTime
            arg:  c,        // 触发后执行函数sendTime时附带的参数
        },
    }
    startTimer(&t.r) // 此处启动定时器,只是把runtimeTimer放到系统协程的堆中,由系统协程维护
    return t
}

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), // Ticker跟Timer的重要区就是提供了period这个参数,据此决定timer是一次性的,还是周期性的
            f:      sendTime,
            arg:    c,
        },
    }
    startTimer(&t.r)
    return t
}

func sendTime(c interface{}, seq uintptr) {
    select {
    case c.(chan Time) <- Now():
    default:
    }
}

sendTime 表示定时器时间到了执行的api。

        timer触发时不会阻塞,因为channel是有缓冲的,把当前的时间塞进去就完事儿了,但是ticker不太一样,ticker 是不断的塞 万一对方没有及时把时间从channel中读走,这个时候sendTime只能执行default了,这次塞数据的操作会被丢弃。

startTimer 就是把当前的timer or ticker 交给系统协程。

        系统有专门的协程管理着所有的timer or ticker,并不是每一个timer or ticker就有一个对应的协程,如果真的是那样的,启动几百个定时器 岂不是要有几百个协程跟着服务 这样效率也太低了。

        系统协程有64个,每个系统协程通过四叉堆管理着其下属的定时器,添加/删除都会涉及到堆的调整,

 类似资料: