=Start=
缘由:
最近在学习Go 语言,所以会在没事的时候多练习一下如何用Go 语言实现一些常规开发任务中的功能,方便以后需要的时候快速参考。
正文:
参考解答:
一个例子:
package main
import (
"log"
"time"
)
func main() {
log.Println("start")
input := make(chan interface{})
// producer - produce the messages to channel
go func() {
for i := 0; i < 5; i++ {
input <- i
}
input <- "hello, world"
}()
t1 := time.NewTimer(time.Second * 5)
t2 := time.NewTimer(time.Second * 10)
for {
// 如果把下面的 (Timer).Reset 注释掉之后,在执行到第10秒的时候就会报错(所有的goroutines都入睡了)
select { // fatal error: all goroutines are asleep - deadlock!
// consumer - consume the messages from channel
case msg := <-input:
time.Sleep(time.Second)
log.Println(msg)
case <-t1.C:
log.Println("5s timer")
t1.Reset(time.Second * 5) // 因为 select 在死循环中,所以需要不断的重置定时器
case <-t2.C:
log.Println("10s timer")
t2.Reset(time.Second * 10)
}
}
}
&
2018/10/24 19:41:53 start 2018/10/24 19:41:54 0 2018/10/24 19:41:55 1 2018/10/24 19:41:56 2 2018/10/24 19:41:57 3 2018/10/24 19:41:58 4 2018/10/24 19:41:58 5s timer 2018/10/24 19:41:59 hello, world 2018/10/24 19:42:03 10s timer 2018/10/24 19:42:03 5s timer 2018/10/24 19:42:08 5s timer 2018/10/24 19:42:13 10s timer 2018/10/24 19:42:13 5s timer 2018/10/24 19:42:18 5s timer 2018/10/24 19:42:23 10s timer 2018/10/24 19:42:23 5s timer 2018/10/24 19:42:28 5s timer 2018/10/24 19:42:33 10s timer 2018/10/24 19:42:33 5s timer ^Csignal: interrupt
刚看到这段代码的时候,对case子句中的t1.C和t2.C比较好奇,所以去看了一下文档:
/*
The Timer type represents a single event. When the Timer expires, the current time will be sent on C, unless the Timer was created by AfterFunc. A Timer must be created with NewTimer or AfterFunc.
Timer类型表示单个事件。当计时器Timer过期时,当前时间将被发送到字段C上,除非计时器Timer是由AfterFunc创建的。计时器Timer必须使用NewTimer或AfterFunc创建。
*/
type Timer struct {
C <-chan Time
// contains filtered or unexported fields
}
// 由Timer的结构说明可知,字段C是一个channel
再结合最初的那个例子来看,在Go 语言中使用Timer来实现定时器的思路就是通过 channel 的阻塞来达到定时调用的目的。在需要周期执行的地方,进行 channel 读取操作,当预定时间到,channel 中被写入值,阻塞解除,调用自定义的函数/功能。
再来一个「高级一点」的样例:
package main
import (
"log"
"reflect"
"time"
)
/*
在 起始时间-start 且后续 每隔-interval 就执行 函数-jobFunc 参数列表为-jobArgs
*/
// sched to start scheduler job at start time by interval duration.
func sched(jobFunc interface{}, start, interval string, jobArgs ...interface{}) {
// 利用反射 reflect 检查参数 jobFunc 是否为函数类型
jobValue := reflect.ValueOf(jobFunc)
if jobValue.Kind() != reflect.Func {
log.Panic("only function can be schedule.")
}
// 检查传入 jobArgs 个数和 jobFunc 参数个数是否相同
if len(jobArgs) != jobValue.Type().NumIn() {
log.Panic("The number of args valid.")
}
// Get job function args.
in := make([]reflect.Value, len(jobArgs))
for i, arg := range jobArgs {
in[i] = reflect.ValueOf(arg)
}
// 解析传入的 interval 间隔
d, err := time.ParseDuration(interval)
if err != nil {
log.Panic(err)
}
location, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
log.Panic(err)
}
t, err := time.ParseInLocation("15:04:05", start, location)
if err != nil {
log.Panic(err)
}
now := time.Now()
// Start time.
t = time.Date(now.Year(), now.Month(), now.Day(), t.Hour(), t.Minute(), t.Second(), 0, location)
if now.After(t) {
t = t.Add((now.Sub(t)/d + 1) * d)
}
// 根据传入的 计划执行时间-start 和 当前时间-now 作比较决定是否需要 Sleep 后再进行第一次调用
log.Println(t.Sub(now))
time.Sleep(t.Sub(now)) // 如果启动时间 start 设置的比当前时间要早,则不 Sleep ,但是定时任务还是会等到下一个整除 interval 的时间点才会执行
go jobValue.Call(in)
// log.Printf("go jobValue.Call(in) => %T.call(%T)", jobValue, in)
// log.Printf("go jobValue.Call(in) => %q.call(%q)", jobValue, in)
// log.Printf("go jobValue.Call(in) => %v.call(%d)", jobValue, in)
// 然后再新建一个 Ticker 用于定时调用
ticker := time.NewTicker(d)
go func() {
for _ = range ticker.C {
// log.Println(ticker)
// time.Sleep(time.Second)
go jobValue.Call(in)
}
}()
}
// 可选参数
func foo(params ...int) {
log.Println(params)
}
// 固定参数
func foo2(params int) {
time.Sleep(time.Duration(200) * time.Millisecond)
log.Println(params)
}
// 固定参数
func foo3(year, day, hour int) {
// time.Sleep(time.Second)
log.Println(year, day, hour)
}
func main() {
now := time.Now()
aChan := make(chan int, 1)
//
// sched(foo, "21:05:35", "5s", now.Year())
// sched(foo, "21:05:35", "5s", now.Year(), now.Month(), now.Day()) // panic: The number of args valid.
// sched(foo2, "21:05:35", "5s", now.Year())
sched(foo3, "21:21:51", "3s", now.Year(), now.Day(), now.Hour())
// sched(now, "20:53:05", "5s", now.Year())
//
// 阻塞主线程,否则主线程执行完就退出了,定时任务就无法执行
<-aChan
}
参考链接:
- 如何用golang实现一个定时器任务队列#nice
- go的timer定时器实现
- Golang 实现简单的定时器 #nice
- golang的定时器
- 论golang Timer Reset方法使用的正确姿势
- golang 定时器
- Go语言版crontab
- a cron library for go
- a cron library for go, updated to have removable jobs
=END=
《 “Go语言编程练习#1-定时器timer” 》 有 3 条评论
层级时间轮的 Golang 实现
http://russellluo.com/2018/10/golang-implementation-of-hierarchical-timing-wheels.html
`
一、引言
二、简单时间轮
三、层级时间轮
四、Kafka 的变体实现
五、Golang 实现要点
六、相关阅读
`
Go 编程:图解反射
https://www.gitdig.com/go-reflect/
https://blog.golang.org/laws-of-reflection
https://research.swtch.com/interfaces
GCTT 出品 | 为什么会存在 Goroutines 泄漏,如何避免?
https://mp.weixin.qq.com/s/czjRREJwC-1j2lI_Pvx86A
https://medium.com/jexia/why-goroutines-leaks-exist-and-how-to-avoid-these-dfc572bdad08