=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