Golang 游戏leaf系列(六) time模块 cron表达式

一、cron表达式

cron表达式详解,cron表达式写法,cron表达式例子

cron表达式,主要用于定时作业(定时任务)系统定义执行时间或执行频率的表达式。Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,Cron有如下两种语法格式:

Seconds Minutes Hours DayofMonth Month DayofWeek Year或 
Seconds Minutes Hours DayofMonth Month DayofWeek

从右边看,就是年+周几+月日时分秒

1.例子
  • 0 0 3 * * ? 每天3点执行
  • 0 5/10 3 * * ?,每天3点的 5分,15分,25分,35分,45分,55分这几个时间点执行,/ 符号前表示开始时间,符号后表示每次递增的值。
  • 0 10 3 ? * 1 每周星期天,3点10分 执行,注:1表示星期天,2表示星期一...7表示星期六
  • */5 * * * * ? 每隔5秒执行一次
  • 0 */1 * * * ? 每隔1分钟执行一次
  • 0 0/3 * * * ? 每三分钟触发一次
  • 0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发
  • 0 0 5-15 * * ? 每天5-15点整点触发,-表示一个范围,如在小时字段中使用“10-12”,则表示从10到12点,即10,11,12
  • 0 0 10,14,16 * * ? 每天上午10点,下午2点,4点,逗号是个列表,如在星期字段中使用“1,2,4”,则表示星期一,星期二,星期四
  • 0 10 3 ? * 1#3 每个月的第三个星期,星期天 执行,#号只能出现在星期的位置

问号只能出现在日期和星期这两个位置,表示这个位置的值不确定。这里简单写一下自己对*和?的理解,举个简单例子,如果我们指定每月2号执行操作,那么每月2号是星期几是不确定的,因此星期字段不能为* ,*表示所有的值,所以要用?,即我不关心2号是周几,它爱是周几是周几。

L("last") ("last") "L" 用在day-of-month字段意思是 "这个月最后一天";用在 day-of-week字段, 它简单意思是 "7" or "SAT"。 如果在day-of-week字段里和数字联合使用,它的意思就是 "这个月的最后一个星期几" – 例如: "6L" means "这个月的最后一个星期五". 当我们用“L”时,不指明一个列表值或者范围是很重要的,不然的话,我们会得到一些意想不到的结果。比如0 15 10 ? * 6L 2002-2005表示 2002年至2005年的每月的最后一个星期五上午10:15触发 ,0 0 23 L * ?表示每月最后一天23点执行一次

二、https://github.com/robfig/cron
package main
//blog: xiaorui.cc
import (
    "github.com/robfig/cron"
    "log"
)
 
func main() {
    i := 0
    c := cron.New()
    spec := "0 */1 * * * *"
    c.AddFunc(spec, func() {
        i++
        log.Println("start", i)
    })
    c.Start()
    select{} //阻塞主线程不退出
 
}

AddFunc注册任务到调度器里,当任务要执行的时候会使用goroutines调用,这样每个任务都不会发生阻塞。其中注意select的用法: golang 的 select 的功能和 select, poll, epoll 相似, 就是监听 IO 操作,当 IO 操作发生时,触发相应的动作。

更多参考golang /robfig/cron库 的学习笔记

三、leaf的AfterFunc

Timer主要是提供一个Cron功能的定时器服务,其中Timer是time.AfterFunc的封装,是为了方便聚合到Skeleton中。

Go 语言标准库提供了定时器的支持:

func AfterFunc(d Duration, f func()) *Timer

AfterFunc 会等待 d 时长后调用 f 函数,这里的 f 函数将在另外一个 goroutine 中执行。Leaf 提供了一个相同的 AfterFunc 函数,相比之下,f 函数在 AfterFunc 的调用 goroutine 中执行,这样就避免了同步机制的使用:

skeleton.AfterFunc(5 * time.Second, func() {
    // ...
})

这个功能也是通过一个通道来做的。

type Dispatcher struct {
    ChanTimer chan *Timer
}

func NewDispatcher(l int) *Dispatcher {
    disp := new(Dispatcher)
    disp.ChanTimer = make(chan *Timer, l)
    return disp
}

// Timer
type Timer struct {
    t  *time.Timer
    cb func()
}

func (disp *Dispatcher) AfterFunc(d time.Duration, cb func()) *Timer {
    t := new(Timer)
    t.cb = cb
    t.t = time.AfterFunc(d, func() {
        disp.ChanTimer <- t
    })
    return t
}

实质上构造了一个Timer类型,包含原生的time.Timer和到期执行的回调cb。然后原生Timer时间到了之后,把这个构造数据塞入通道disp.ChanTimer。在skeleton.Run里,会自动执行这些Cb

// leaf\module\skeleton.go
func (s *Skeleton) Run(closeSig chan bool) {
    for {
        select {
        case <-closeSig:
            s.commandServer.Close()
            s.server.Close()
            for !s.g.Idle() || !s.client.Idle() {
                s.g.Close()
                s.client.Close()
            }
            return
        case ri := <-s.client.ChanAsynRet:
            s.client.Cb(ri)

        // 等待来自通道的数据
        case ci := <-s.server.ChanCall:
            s.server.Exec(ci)

        case ci := <-s.commandServer.ChanCall:
            s.commandServer.Exec(ci)
        case cb := <-s.g.ChanCb:
            s.g.Cb(cb)
        case t := <-s.dispatcher.ChanTimer:
            t.Cb()
        }
    }
}

在官方example_test.go中是手动执行(<-d.ChanTimer).Cb().并且还演示了调用Stop方法将会清除一个定时器。

func ExampleTimer() {
    d := timer.NewDispatcher(10)

    // timer 1
    d.AfterFunc(1, func() {
        fmt.Println("My name is Leaf")
    })

    // timer 2
    t := d.AfterFunc(1, func() {
        fmt.Println("will not print")
    })
    t.Stop()

    // dispatch
    (<-d.ChanTimer).Cb()

    // Output:
    // My name is Leaf
}
四、leaf的cron功能

看起来,是在github.com/robfig/cron库的基础上来做的。源码如下

// Cron
type Cron struct {
    t *Timer
}

func (c *Cron) Stop() {
    if c.t != nil {
        c.t.Stop()
    }
}

func (disp *Dispatcher) CronFunc(cronExpr *CronExpr, _cb func()) *Cron {
    c := new(Cron)

    now := time.Now()
    nextTime := cronExpr.Next(now)
    if nextTime.IsZero() {
        return c
    }

    // callback
    var cb func()
    cb = func() {
        defer _cb()

        now := time.Now()
        nextTime := cronExpr.Next(now)
        if nextTime.IsZero() {
            return
        }
        c.t = disp.AfterFunc(nextTime.Sub(now), cb)
    }

    c.t = disp.AfterFunc(nextTime.Sub(now), cb)
    return c
}

源码中使用了嵌套的倒计时调用cb。

func ExampleCron() {
    d := timer.NewDispatcher(10)

    // cron expr
    cronExpr, err := timer.NewCronExpr("0/3 * * * * *")
    if err != nil {
        fmt.Println("cron expr error:",err)
        return
    }

    // cron
    var c *timer.Cron
    c = d.CronFunc(cronExpr, func() {
        fmt.Println("My name is Leaf")
        //c.Stop()
    })

    fmt.Println("begin cron",c)
    // dispatch
    (<-d.ChanTimer).Cb()
    select{
        case t := <-d.ChanTimer:
            t.Cb()
    }


    // Output:
    // My name is Leaf
}

虽然我把c.Stop给注释掉了,但也没有像我预想中一样,每隔3秒就打印一次My name is Leaf

    cronExpr, err := timer.NewCronExpr("0/3 * * * * *")
    now := time.Now()
    nextTime := cronExpr.Next(now)
    if nextTime.IsZero() {
        fmt.Println("nextTime is Zero")
    }
    fmt.Println("sub time",nextTime.Sub(now))

这个时间也不准呀:

sub time 1.8513635s
五、其它
1.issues 使用标准库的timer要加同步处理吗?

不使用同步可能引起问题,一定概率发生(可能通常都不发生),可能在用户量比较大的时候才出现。你发现的 Leaf 的 timer 会延时是因为某一些情况下,定时器到了触发时间了,但是现在还没机会执行,所以它就排队等待(可以认为是当前 goroutine 繁忙,需要处理其他代码),而 go 的 timer 额外开了一个 goroutine 来执行,所以无需等待(一定程度上来说是)。

实际上,这个问题是服务器帧率控制的问题。而帧率控制其实是动态的,而不是固定一个时间间隔,你可以看看 https://github.com/mangos/mangosd/blob/master/WorldRunnable.cpp#L78 的实现方法,思考一下。在 Leaf 中最好的帧率控制应该实现在 select 的 default 中。更多的东西就需要你自己探索了

转载于:https://www.jianshu.com/p/b362e45e55a3

猜你喜欢

转载自blog.csdn.net/weixin_33881041/article/details/91076097