Golang time.Timer and time.Ticker

time.Timer

结构

首先我们看Timer的结构定义:

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

其中有一个C的只读channel,还有一个runtimeTimer类型的结构体,再看一下这个结构的具体结构:

type runtimeTimer struct {
    tb uintptr
    i  int

    when   int64
    period int64
    f      func(interface{}, uintptr) // NOTE: must not be closure
    arg    interface{}
    seq    uintptr
}

在使用定时器Timer的时候都是通过 NewTimerAfterFunc 函数来获取。
先来看一下NewTimer的实现:

func NewTimer(d Duration) *Timer {
    c := make(chan Time, 1)
    t := &Timer{
        C: c,
        r: runtimeTimer{
            when: when(d), //表示达到时间段d时候调用f
            f:    sendTime,  // f表示一个函数调用,这里的sendTime表示d时间到达时向Timer.C发送当前的时间
            arg:  c,  // arg表示在调用f的时候把参数arg传递给f,c就是用来接受sendTime发送时间的
        },
    }
    startTimer(&t.r)
    return t
}

定时器的具体实现逻辑,都在 runtime 中的 time.go 中,它的实现,没有采用经典 Unix 间隔定时器 setitimer 系统调用,也没有 采用 POSIX间隔式定时器(相关系统调用:timer_createtimer_settimetimer_delete),而是通过四叉树堆(heep)实现的(runtimeTimer 结构中的i字段,表示在堆中的索引)。通过构建一个最小堆,保证最快拿到到期了的定时器执行。定时器的执行,在专门的 goroutine 中进行的:go timerproc()。有兴趣的同学,可以阅读 runtime/time.go 的源码。

其他方法

  • func After(d Duration) <-chan Time { return NewTimer(d).C }

根据源码可以看到After直接是返回了Timerchannel,这种就可以做超时处理。
比如我们有这样一个需求:我们写了一个爬虫,爬虫在HTTP GET 一个网页的时候可能因为网络的原因就一只等待着,这时候就需要做超时处理,比如只请求五秒,五秒以后直接丢掉不请求这个网页了,或者重新发起请求。

go Get("http://baidu.com/")
 
func Get(url string) {
    response := make(chan string)
    response = http.Request(url)

    select {
    case html :=<- response:
        println(html)
    case <-time.After(time.Second * 5):
        println("超时处理")
    }
}

可以从代码中体现出来,如果五秒到了,网页的请求还没有下来就是执行超时处理,因为Timer的内部会是帮你在你设置的时间长度后自动向Timer.C中写入当前时间。

其实也可以写成这样:

func Get(url string) {
    response := make(chan string)
    response = http.Request(url)
    timeOut := time.NewTimer(time.Second * 3)
    select {
    case html :=<- response:
        println(html)
    case <-timeOut.C:
        println("超时处理")
    }
}
  • func (t *Timer) Reset(d Duration) bool //强制的修改timer中规定的时间,Reset会先调用 stopTimer 再调用 startTimer,类似于废弃之前的定时器,重新启动一个定时器,ResetTimer还未触发时返回true;触发了或Stop了,返回false
  • func (t *Timer) Stop() bool // 如果定时器还未触发,Stop 会将其移除,并返回 true;否则返回 false;后续再对该 Timer 调用 Stop,直接返回 false

比如我写了了一个简单的事例:每两秒给你的女票发送一个"I Love You!"

// 其中协程之间的控制做的不太好,可以使用channel或者golang中的context来控制
package main

import (
    "time"
    "fmt"
    )

func main() {

    go Love() // 起一个协程去执行定时任务

    stop := 0
    for {
        fmt.Scan(&stop)
        if stop == 1{
            break
        }
    }
}
func Love() {
    timer := time.NewTimer(2 * time.Second)  // 新建一个Timer

    for {
        select {
        case <-timer.C:
            fmt.Println("I Love You!")
            timer.Reset(2 * time.Second)  // 上一个when执行完毕重新设置
        }
    }
    return
}
  • func AfterFunc(d Duration, f func()) *Timer // 在时间d后自动执行函数f
func main() {
    f := func(){fmt.Println("I Love You!")}
    time.AfterFunc(time.Second*2, f)
    time.Sleep(time.Second * 4)

}

自动在2秒后打印 "I Love You!"

time.Ticker

如果学会了Timer那么Ticker就很简单了,TimerTicker结构体的结构是一样的,举一反三,其实Ticker就是一个重复版本的Timer,它会重复的在时间d后向Ticker中写数据

  • func NewTicker(d Duration) *Ticker // 新建一个Ticker
  • func (t *Ticker) Stop() // 停止Ticker
  • func Tick(d Duration) <-chan Time // Ticker.C 的封装

TickerTimer 类似,区别是:Ticker 中的runtimeTimer字段的 period 字段会赋值为 NewTicker(d Duration) 中的d,表示每间隔d纳秒,定时器就会触发一次。

除非程序终止前定时器一直需要触发,否则,不需要时应该调用 Ticker.Stop 来释放相关资源。

如果程序终止前需要定时器一直触发,可以使用更简单方便的 time.Tick 函数,因为 Ticker 实例隐藏起来了,因此,该函数启动的定时器无法停止。

那么这样我们就可以把发"I Love You!"的例子写得简单一些。

func main() {
    //定义一个ticker
    ticker := time.NewTicker(time.Millisecond * 500)
    //Ticker触发
    go func() {
        for t := range ticker.C {
            fmt.Println(t)
            fmt.Println("I Love You!")
        }
    }()

    time.Sleep(time.Second * 18)
    //停止ticker
    ticker.Stop()
}

定时器的实际应用

在实际开发中,定时器用的较多的会是 Timer,如模拟超时,而需要类似 Tiker 的功能时,可以使用实现了 cron spec 的库 cron

参考
定时器

全部评论

相关推荐

10-27 17:26
东北大学 Java
点赞 评论 收藏
分享
死在JAVA的王小美:哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈,我也是,让我免了一轮,但是硬气拒绝了
点赞 评论 收藏
分享
评论
点赞
收藏
分享
牛客网
牛客企业服务