多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
# 4.4 定时器 # 定时器是进程规划自己在未来某一时刻接获通知的一种机制。本节介绍两种定时器:`Timer`(到达指定时间触发且只触发一次)和 `Ticker`(间隔特定时间触发)。 ## Timer ### 内部实现源码分析 `Timer` 类型代表单次时间事件。当 `Timer` 到期时,当时的时间会被发送给 C (channel),除非 `Timer` 是被 `AfterFunc` 函数创建的。 注意:`Timer` 的实例必须通过 `NewTimer` 或 `AfterFunc` 获得。 类型定义如下: ``` type Timer struct { C <-chan Time // The channel on which the time is delivered. r runtimeTimer } ``` C 已经解释了,我们看看 `runtimeTimer`。它定义在 sleep.go 文件中,必须和 `runtime` 包中 `time.go` 文件中的 `timer` 必须保持一致: ``` type timer struct { i int // heap index // Timer wakes up at when, and then at when+period, ... (period > 0 only) // each time calling f(now, arg) in the timer goroutine, so f must be // a well-behaved function and not block. when int64 period int64 f func(interface{}, uintptr) arg interface{} seq uintptr } ``` 我们通过 `NewTimer()` 来看这些字段都怎么赋值,是什么用途。 ``` // NewTimer creates a new Timer that will send // the current time on its channel after at least duration d. 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 } ``` 在 `when` 表示的时间到时,会往 Timer.C 中发送当前时间。`when` 表示的时间是纳秒时间,正常通过 `runtimeNano() + int64(d)` 赋值。跟上一节中讲到的 `now()` 类似,`runtimeNano()` 也在 `runtime` 中实现(`runtime·nanotime`): * 调用系统调用 `clock_gettime` 获取时钟值(这是 POSIX 时钟)。其中 clockid_t 时钟类型是 CLOCK_MONOTONIC,也就是不可设定的恒定态时钟。具体的是什么时间,SUSv3 规定始于未予规范的过去某一点,Linux 上,始于系统启动。 * 如果 `clock_gettime` 不存在,则使用精度差些的系统调用 `gettimeofday`。 `f` 参数的值是 `sendTime`,定时器时间到时,会调用 f,并将 `arg` 和 `seq` 传给 `f`。 因为 `Timer` 是一次性的,所以 `period` 保留默认值 0。 定时器的具体实现逻辑,都在 `runtime` 中的 `time.go` 中,它的实现,没有采用经典 Unix 间隔定时器 `setitimer` 系统调用,也没有 采用 POSIX 间隔式定时器(相关系统调用:`timer_create`、`timer_settime` 和 `timer_delete`),而是通过四叉树堆(heep)实现的(`runtimeTimer` 结构中的 `i` 字段,表示在堆中的索引)。通过构建一个最小堆,保证最快拿到到期了的定时器执行。定时器的执行,在专门的 `goroutine` 中进行的:`go timerproc()`。有兴趣的同学,可以阅读 `runtime/time.go` 的源码。 ### Timer 相关函数或方法的使用 **通过 `time.After` 模拟超时:** ``` c := make(chan int) go func() { // time.Sleep(1 * time.Second) time.Sleep(3 * time.Second) <-c }() select { case c <- 1: fmt.Println("channel...") case <-time.After(2 * time.Second): close(c) fmt.Println("timeout...") } ``` **`time.Stop` 停止定时器 或 `time.Reset` 重置定时器** ``` start := time.Now() timer := time.AfterFunc(2*time.Second, func() { fmt.Println("after func callback, elaspe:", time.Now().Sub(start)) }) time.Sleep(1 * time.Second) // time.Sleep(3*time.Second) // Reset 在 Timer 还未触发时返回 true;触发了或Stop了,返回false if timer.Reset(3 * time.Second) { fmt.Println("timer has not trigger!") } else { fmt.Println("timer had expired or stop!") } time.Sleep(10 * time.Second) // output: // timer has not trigger! // after func callback, elaspe: 4.00026461s ``` 如果定时器还未触发,`Stop` 会将其移除,并返回 true;否则返回 false;后续再对该 `Timer` 调用 `Stop`,直接返回 false。 `Reset` 会先调用 `stopTimer` 再调用 `startTimer`,类似于废弃之前的定时器,重新启动一个定时器。返回值和 `Stop` 一样。 ### Sleep 的内部实现 查看 `runtime/time.go` 文件中的 `timeSleep` 可知,`Sleep` 的是通过 `Timer` 实现的,把当前 goroutine 作为 `arg` 参数(`getg()`)。 ## Ticker 相关函数或方法的使用 `Ticker` 和 `Timer` 类似,区别是:`Ticker` 中的`runtimeTimer` 字段的 `period` 字段会赋值为 `NewTicker(d Duration)` 中的 `d`,表示每间隔 `d` 纳秒,定时器就会触发一次。 除非程序终止前定时器一直需要触发,否则,不需要时应该调用 `Ticker.Stop` 来释放相关资源。 如果程序终止前需要定时器一直触发,可以使用更简单方便的 `time.Tick` 函数,因为 `Ticker` 实例隐藏起来了,因此,该函数启动的定时器无法停止。 ## 定时器的实际应用 在实际开发中,定时器用的较多的会是 `Timer`,如模拟超时,而需要类似 `Tiker` 的功能时,可以使用实现了 `cron spec` 的库 [cron](https://github.com/robfig/cron),感兴趣的可以参考文章:[《Go语言版crontab》](http://blog.studygolang.com/2014/02/go_crontab/)。 # 导航 # - 上一节:[Time类型详解](04.3.md) - 下一节:[Unix 时间相关系统调用](04.5.md)