Go语言编程练习#1-定时器timer


=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
}

 

参考链接:

=END=

,

《 “Go语言编程练习#1-定时器timer” 》 有 3 条评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注