Golang——Cron 定時任務

jihite發表於2020-05-01

開門見山寫一個

package main

import (
    "fmt"
    "github.com/robfig/cron"
    "log"
    "strings"
    "time"
)

func CronTask() {
    log.Println("********  *******  *******")
}

func CronTest() {
    log.Println("Starting Cron...")

    c := cron.New()
    c.AddFunc("* * * * * *", CronTask)  //2 * * * * *, 2 表示每分鐘的第2s執行一次
    c.Start()

    t1 := time.NewTimer(time.Second * 10) // ?time.Second * 10 啥意思? *100行嗎?
    for {
        select {
        case <-t1.C:
            fmt.Println("Time now:", time.Now().Format("2006-01-02 15:04:05")) // 為何要專門制定這個時間
            t1.Reset(time.Second * 10)
        }
    }
}


func main() {
    fmt.Println(strings.Repeat("START ", 15))
    CronTest()
    fmt.Println(strings.Repeat("END ", 15))
}

核心的定時器程式碼就3行

c := cron.New()
c.AddFunc("* * * * * *", CronTask)
c.Start()

那後面那些程式碼時作甚的?

一開始看到示例程式碼時,有個疑惑,如程式碼中註釋

t1 := time.NewTimer(time.Second * 10)

這裡time.Second*10是幹啥的? 是否可以寫成*100呢, 改了後原來是可以的,那更疑惑了既然都行為啥還要寫個這個?

還有後面的for-select-case也是一臉懵逼~~~~

執行程式碼,從結果反推下原理吧,一次執行結果

START START START START START START START START START START START START START START START 
2020/05/01 07:38:07 Starting Cron...
2020/05/01 07:38:08 ********  *******  *******
2020/05/01 07:38:09 ********  *******  *******
2020/05/01 07:38:10 ********  *******  *******
2020/05/01 07:38:11 ********  *******  *******
2020/05/01 07:38:12 ********  *******  *******
2020/05/01 07:38:13 ********  *******  *******
2020/05/01 07:38:14 ********  *******  *******
2020/05/01 07:38:15 ********  *******  *******
2020/05/01 07:38:16 ********  *******  *******
2020/05/01 07:38:17 ********  *******  *******
Time now: 2020-05-01 07:38:17
2020/05/01 07:38:18 ********  *******  *******
2020/05/01 07:38:19 ********  *******  *******
2020/05/01 07:38:20 ********  *******  *******
2020/05/01 07:38:21 ********  *******  *******
2020/05/01 07:38:22 ********  *******  *******
2020/05/01 07:38:23 ********  *******  *******
2020/05/01 07:38:24 ********  *******  *******
2020/05/01 07:38:25 ********  *******  *******
2020/05/01 07:38:26 ********  *******  *******
2020/05/01 07:38:27 ********  *******  *******
Time now: 2020-05-01 07:38:27
2020/05/01 07:38:28 ********  *******  *******

以上是執行的片段,有兩大發現

  • 有START START START。。。沒有END END END 。。。。:說明了程式碼在執行時阻塞在定時器裡,定時器沒有執行完,永遠不會執行END
  • Time now打出來的間隔正好是10s

哦,原來time.NewTimer是個定時器,當這個時間間隔完了後再重新開啟一個。for-select-case 這一塊目的是阻塞流程,不讓程式結束。 理解對嗎

如果是這樣,去掉for-select-case 執行第一個定時器時也可以停10s,是這樣嗎?試驗下:遮蔽掉for-select-case, 輸出

START START START START START START START START START START START START START START START 
2020/05/01 07:56:22 Starting Cron...
END END END END END END END END END END END END END END END 

打臉了,看來阻塞主要靠for-select-case實現,那原理是什麼呢?

去掉t1.Reset效果咋樣呢?

t1 := time.NewTimer(time.Second * 10) // ?time.Second * 10 啥意思? *100行嗎?
    for {
        fmt.Println("hihihihi")
        select {
        case <-t1.C:
            fmt.Println("hello")
        }
    }

輸出

START START START START START START START START START START START START START START START 
2020/05/01 08:12:21 Starting Cron...
hihihihi
2020/05/01 08:12:22 ********  *******  *******
2020/05/01 08:12:23 ********  *******  *******
2020/05/01 08:12:24 ********  *******  *******
2020/05/01 08:12:25 ********  *******  *******
2020/05/01 08:12:26 ********  *******  *******
2020/05/01 08:12:27 ********  *******  *******
2020/05/01 08:12:28 ********  *******  *******
2020/05/01 08:12:29 ********  *******  *******
2020/05/01 08:12:30 ********  *******  *******
2020/05/01 08:12:31 ********  *******  *******
hello
hihihihi
2020/05/01 08:12:32 ********  *******  *******
2020/05/01 08:12:33 ********  *******  *******
2020/05/01 08:12:34 ********  *******  *******
2020/05/01 08:12:35 ********  *******  *******
2020/05/01 08:12:36 ********  *******  *******

更蒙了,去掉reset, 執行完第一個定時器10s, 非但沒聽,還直接執行起來了,沒停了

for 迴圈裡的print不是刷刷的一大片,而是和case命中時一期打,看來是時候瞭解下select-case的原理了

select case

按慣例先上個例子

package main

import (
    "fmt"
    "strings"
)

func SelectTest() {
    intChan := make(chan int, 1)
    stringChan := make(chan string, 1)
    intChan <- 123456
    stringChan <- "hello"

    select {
    case value := <-intChan:
        fmt.Println(value)
    case value := <- stringChan:
        fmt.Println(value)
    }
}


func main() {
    fmt.Println(strings.Repeat("START ", 15))
    SelectTest()
    fmt.Println(strings.Repeat("END ", 15))
}

執行多次可以看到,輸出的結果是 123456、"hello"不定

select 語法

每個case都必須是個通訊

如果一個通訊可進行它就執行,其他被忽略

如果有多個case可執行,就會隨機的選擇一個執行

如果沒有case可執行,如果如果有default,執行default語句;否則就阻塞,直到有某個通訊可行

這裡還是有很多問題,單開一節弄清楚 select語句

再回到一開始的定時任務

回顧正題,定時原理解析

t1 := time.NewTimer(time.Second * 10) 
    for {
        select {
        case <-t1.C:
            fmt.Println("Time now:", time.Now().Format("2006-01-02 15:04:05"))
            t1.Reset(time.Second * 10)
        }
    }
  • 生成一個定時器t1, 執行for迴圈,一開始定時時間(10s)未到, 也沒有阻塞任務,它就阻塞在case中;
  • 定時時間到了,則執行case中語句;
  • 然後又重新恢復定時時長
  • 重新走for迴圈,還是重複上面的故事 

上面程式碼中嘗試把case中的t1.Reset去掉,結果也是定時任務不同的執行,原因是執行case中的語句後,接著執行for迴圈,由於沒有新通訊過來(case語句永遠無法滿足),同時沒有default語句,所以同樣可以阻塞再次。

相比在case中t1 Reset, t1 Reset更靈活些,因為可以再每次重新滿足case時做一些靈活的操作,比如跳出迴圈,做一些統計列印等。

 

相關文章