這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
cron定時計劃任務,golang的cron開源項目程式碼分析:https://github.com/robfig/cron
package cronimport ("log""runtime""sort""time")// Cron keeps track of any number of entries, invoking the associated func as// specified by the schedule. It may be started, stopped, and the entries may// be inspected while running.type Cron struct {entries []*Entry //Entry是所有任務封裝的結構stop chan struct{} //關閉flag的channeladd chan *Entry //新增的Entrysnapshot chan []*Entry //Entry的備份running bool //開啟flagErrorLog *log.Loggerlocation *time.Location}// Job is an interface for submitted cron jobs.type Job interface {Run() //Job只有一個Run方法}// The Schedule describes a job's duty cycle.type Schedule interface {// Return the next activation time, later than the given time.// Next is invoked initially, and then each time the job is run.Next(time.Time) time.Time //調度選取下一次執行的時候,整個流程執行非常關鍵不住}// Entry consists of a schedule and the func to execute on that schedule.type Entry struct {// The schedule on which this job should be run.Schedule Schedule// The next time the job will run. This is the zero time if Cron has not been// started or this entry's schedule is unsatisfiableNext time.Time //下一次啟動並執行時間// The last time this job was run. This is the zero time if the job has never// been run.Prev time.Time //上一次啟動並執行時間// The Job to run.Job Job}// byTime is a wrapper for sorting the entry array by time// (with zero time at the end).type byTime []*Entryfunc (s byTime) Len() int { return len(s) }func (s byTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] }func (s byTime) Less(i, j int) bool {// 比較時間,兩個都是0的時候直接返回false,0比任何時間優先順序高if s[i].Next.IsZero() {return false}if s[j].Next.IsZero() {return true}return s[i].Next.Before(s[j].Next)}// New returns a new Cron job runner, in the Local time zone.func New() *Cron {return NewWithLocation(time.Now().Location())}// NewWithLocation returns a new Cron job runner.func NewWithLocation(location *time.Location) *Cron {return &Cron{entries: nil,add: make(chan *Entry), //新增entry的channelstop: make(chan struct{}),snapshot: make(chan []*Entry),running: false,ErrorLog: nil,location: location, //時間}}// A wrapper that turns a func() into a cron.Jobtype FuncJob func()func (f FuncJob) Run() { f() }// AddFunc adds a func to the Cron to be run on the given schedule.func (c *Cron) AddFunc(spec string, cmd func()) error {return c.AddJob(spec, FuncJob(cmd))}// AddJob adds a Job to the Cron to be run on the given schedule.func (c *Cron) AddJob(spec string, cmd Job) error {schedule, err := Parse(spec)if err != nil {return err}c.Schedule(schedule, cmd)return nil}// Schedule adds a Job to the Cron to be run on the given schedule.func (c *Cron) Schedule(schedule Schedule, cmd Job) {entry := &Entry{Schedule: schedule,Job: cmd,}if !c.running {c.entries = append(c.entries, entry)return}c.add <- entry}//當前啟動並執行所有Entry的一份拷貝,方便測試和跟蹤func (c *Cron) Entries() []*Entry {if c.running {//先傳一個nil,觸發channel的讀時間,run的協程複製entry,然後讀取出所有的entryc.snapshot <- nilx := <-c.snapshotreturn x}return c.entrySnapshot()}// Location gets the time zone locationfunc (c *Cron) Location() *time.Location {return c.location}//Start方法和Run方法的區別就是在不在單獨的go-routine裡面運行// Start the cron scheduler in its own go-routine, or no-op if already started.func (c *Cron) Start() {if c.running {return}c.running = truego c.run()}// Run the cron scheduler, or no-op if already running.func (c *Cron) Run() {if c.running {return}//開始cron的運行c.running = truec.run()}func (c *Cron) runWithRecovery(j Job) {defer func() {//運行job panic的時候日誌記錄棧資訊if r := recover(); r != nil {const size = 64 << 10buf := make([]byte, size)buf = buf[:runtime.Stack(buf, false)]c.logf("cron: panic running job: %v\n%s", r, buf)}}()j.Run()}// Run the scheduler. this is private just due to the need to synchronize// access to the 'running' state variable.func (c *Cron) run() {// Figure out the next activation times for each entry.now := c.now()for _, entry := range c.entries {entry.Next = entry.Schedule.Next(now)}for {// 從小到大的排序entrysort.Sort(byTime(c.entries))var timer *time.Timerif len(c.entries) == 0 || c.entries[0].Next.IsZero() {// 沒有entry的時候設定超大的timer,依然可以處理stoptimer = time.NewTimer(100000 * time.Hour)} else {timer = time.NewTimer(c.entries[0].Next.Sub(now))}for {select {case now = <-timer.C:now = now.In(c.location)// 運行比now時間小的entryfor _, e := range c.entries {if e.Next.After(now) || e.Next.IsZero() {break}//執行任務Jobgo c.runWithRecovery(e.Job)e.Prev = e.Nexte.Next = e.Schedule.Next(now)}case newEntry := <-c.add://新增entry時候,停掉上一次的timer,timer.Stop()now = c.now()newEntry.Next = newEntry.Schedule.Next(now)c.entries = append(c.entries, newEntry)case <-c.snapshot:c.snapshot <- c.entrySnapshot()continuecase <-c.stop:timer.Stop()return}break}}}// Logs an error to stderr or to the configured error logfunc (c *Cron) logf(format string, args ...interface{}) {if c.ErrorLog != nil {c.ErrorLog.Printf(format, args...)} else {log.Printf(format, args...)}}// Stop stops the cron scheduler if it is running; otherwise it does nothing.func (c *Cron) Stop() {if !c.running {return}c.stop <- struct{}{}c.running = false}// entrySnapshot returns a copy of the current cron entry list.func (c *Cron) entrySnapshot() []*Entry {entries := []*Entry{}for _, e := range c.entries {entries = append(entries, &Entry{Schedule: e.Schedule,Next: e.Next,Prev: e.Prev,Job: e.Job,})}return entries}// now returns current time in c locationfunc (c *Cron) now() time.Time {return time.Now().In(c.location)}
其實關鍵就是Schedule的Next的方法的具體實現