chinese直男口爆体育生外卖, 99久久er热在这里只有精品99, 又色又爽又黄18禁美女裸身无遮挡, gogogo高清免费观看日本电视,私密按摩师高清版在线,人妻视频毛茸茸,91论坛 兴趣闲谈,欧美 亚洲 精品 8区,国产精品久久久久精品免费

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線(xiàn)課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

解析Golang定時(shí)任務(wù)庫(kù)gron設(shè)計(jì)和原理

Linux愛(ài)好者 ? 來(lái)源:Linux愛(ài)好者 ? 作者:Linux愛(ài)好者 ? 2022-12-15 13:57 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

從 cron 說(shuō)起

在 Unix-like 操作系統(tǒng)中,有一個(gè)大家都很熟悉的 cli 工具,它能夠來(lái)處理定時(shí)任務(wù),周期性任務(wù),這就是: cron。 你只需要簡(jiǎn)單的語(yǔ)法控制就能實(shí)現(xiàn)任意【定時(shí)】的語(yǔ)義。用法上可以參考一下這個(gè) Crontab Guru Editor[1],做的非常精巧。

cb04c2ac-7c3c-11ed-8abf-dac502259ad0.jpg

簡(jiǎn)單說(shuō),每一個(gè)位都代表了一個(gè)時(shí)間維度,* 代表全集,所以,上面的語(yǔ)義是:在每天早上的4點(diǎn)05分觸發(fā)任務(wù)。

但 cron 畢竟只是一個(gè)操作系統(tǒng)級(jí)別的工具,如果定時(shí)任務(wù)失敗了,或者壓根沒(méi)啟動(dòng),cron 是沒(méi)法提醒開(kāi)發(fā)者這一點(diǎn)的。并且,cron 和 正則表達(dá)式都有一種魔力,不知道大家是否感同身受,這里引用同事的一句名言:

這世界上有些語(yǔ)言非常相似: shell腳本, es查詢(xún)的那個(gè)dsl語(yǔ)言, 定時(shí)任務(wù)的crontab, 正則表達(dá)式. 他們相似就相似在每次要寫(xiě)的時(shí)候基本都得重新現(xiàn)學(xué)一遍。

正巧,最近看到了 gron 這個(gè)開(kāi)源項(xiàng)目,它是用 Golang 實(shí)現(xiàn)一個(gè)并發(fā)安全的定時(shí)任務(wù)庫(kù)。實(shí)現(xiàn)非常簡(jiǎn)單精巧,代碼量也不多。今天我們就來(lái)一起結(jié)合源碼看一下,怎樣基于 Golang 的能力做出來(lái)一個(gè)【定時(shí)任務(wù)庫(kù)】。

gron

Gron provides a clear syntax for writing and deploying cron jobs.

gron[2] 是一個(gè)泰國(guó)小哥在 2016 年開(kāi)源的作品,它的特點(diǎn)就在于非常簡(jiǎn)單和清晰的語(yǔ)義來(lái)定義【定時(shí)任務(wù)】,你不用再去記 cron 的語(yǔ)法。我們來(lái)看下作為使用者怎樣上手。

首先,我們還是一個(gè) go get 安裝依賴(lài):

$gogetgithub.com/roylee0704/gron

假設(shè)我們期望在【時(shí)機(jī)】到了以后,要做的工作是打印一個(gè)字符串,每一個(gè)小時(shí)執(zhí)行一次,我們就可以這樣:

packagemain

import(
"fmt"
"time"
"github.com/roylee0704/gron"
)

funcmain(){
c:=gron.New()
c.AddFunc(gron.Every(1*time.Hour),func(){
fmt.Println("runseveryhour.")
})
c.Start()
}

非常簡(jiǎn)單,而且即便是在 c.Start 之后我們依然可以添加新的定時(shí)任務(wù)進(jìn)去。支持了很好的擴(kuò)展性。

定時(shí)參數(shù)

注意到我們調(diào)用 gron.New().AddFunc() 時(shí)傳入了一個(gè) gron.Every(1*time.Hour)。

這里其實(shí)你可以傳入任何一個(gè) time.Duration,從而把調(diào)度間隔從 1 小時(shí)調(diào)整到 1 分鐘甚至 1 秒。

除此之外,gron 還很貼心地封裝了一個(gè) xtime 包用來(lái)把常見(jiàn)的 time.Duration 封裝起來(lái),這里我們開(kāi)箱即用。

import"github.com/roylee0704/gron/xtime"

gron.Every(1*xtime.Day)
gron.Every(1*xtime.Week)

很多時(shí)候我們不僅僅某個(gè)任務(wù)在當(dāng)天運(yùn)行,還希望是我們指定的時(shí)刻,而不是依賴(lài)程序啟動(dòng)時(shí)間,機(jī)械地加 24 hour。gron 對(duì)此也做了很好的支持:

gron.Every(30*xtime.Day).At("00:00")
gron.Every(1*xtime.Week).At("23:59")

我們只需指定 At("hh:mm") 就可以實(shí)現(xiàn)在指定時(shí)間執(zhí)行。

源碼解析

這一節(jié)我們來(lái)看看 gron 的實(shí)現(xiàn)原理。

所謂定時(shí)任務(wù),其實(shí)包含兩個(gè)層面:

  1. 觸發(fā)器。即我們希望這個(gè)任務(wù)在什么時(shí)間點(diǎn),什么周期被觸發(fā);

  2. 任務(wù)。即我們?cè)谟|發(fā)之后,希望執(zhí)行的任務(wù),類(lèi)比到我們上面示例的 fmt.Println。

對(duì)這兩個(gè)概念的封裝和擴(kuò)展是一個(gè)定時(shí)任務(wù)庫(kù)必須考慮的。

而同時(shí),我們是在 Golang 的協(xié)程上跑程序的,意味著這會(huì)是一個(gè)長(zhǎng)期運(yùn)行的協(xié)程,否則你即便指定了【一個(gè)月后干XXX】這個(gè)任務(wù),程序兩天后掛了,也就無(wú)法實(shí)現(xiàn)你的訴求了。

所以,我們還希望有一個(gè) manager 的角色,來(lái)管理我們的一組【定時(shí)任務(wù)】,如何調(diào)度,什么時(shí)候啟動(dòng),怎么停止,啟動(dòng)了以后還想加新任務(wù)是否支持。

Cron

在 gron 的體系里,Cron 對(duì)象(我們上面通過(guò) gron.New 創(chuàng)建出來(lái)的)就是我們的 manager,而底層的一個(gè)個(gè)【定時(shí)任務(wù)】則對(duì)應(yīng)到 Cron 對(duì)象中的一個(gè)個(gè) Entry:

//Cronprovidesaconvenientinterfaceforschedulingjobsuchastoclean-up
//databaseentryeverymonth.
//
//Cronkeepstrackofanynumberofentries,invokingtheassociatedfuncas
//specifiedbytheschedule.Itmayalsobestarted,stoppedandtheentries
//maybeinspected.
typeCronstruct{
entries[]*Entry
runningbool
addchan*Entry
stopchanstruct{}
}

//NewinstantiatesnewCroninstantc.
funcNew()*Cron{
return&Cron{
stop:make(chanstruct{}),
add:make(chan*Entry),
}
}
  • entries 就是定時(shí)任務(wù)的核心能力,它記錄了一組【定時(shí)任務(wù)】;

  • running 用來(lái)標(biāo)識(shí)這個(gè) Cron 是否已經(jīng)啟動(dòng);

  • add 是一個(gè)channel,用來(lái)支持在 Cron 啟動(dòng)后,新增的【定時(shí)任務(wù)】;

  • stop 同樣是個(gè)channel,注意到是空結(jié)構(gòu)體,用來(lái)控制 Cron 的停止。這個(gè)其實(shí)是經(jīng)典寫(xiě)法了,對(duì)日常開(kāi)發(fā)也有借鑒意義,我們待會(huì)兒會(huì)好好看一下。

我們觀察到,當(dāng)調(diào)用 gron.New() 方法后,得到的是一個(gè)指向 Cron 對(duì)象的指針。此時(shí)只是初始化了 stop 和 add 兩個(gè) channel,沒(méi)有啟動(dòng)調(diào)度。

Entry

重頭戲來(lái)了,Cron 里面的 []* Entry 其實(shí)就代表了一組【定時(shí)任務(wù)】,每個(gè)【定時(shí)任務(wù)】可以簡(jiǎn)化理解為 <觸發(fā)器,任務(wù)> 組成的一個(gè) tuple。

//Entryconsistsofascheduleandthejobtobeexecutedonthatschedule.
typeEntrystruct{
ScheduleSchedule
JobJob

//thenexttimethejobwillrun.ThisiszerotimeifCronhasnotbeen
//startedorinvalidschedule.
Nexttime.Time

//thelasttimethejobwasrun.Thisiszerotimeifthejobhasnotbeen
//run.
Prevtime.Time
}

//ScheduleistheinterfacethatwrapsthebasicNextmethod.
//
//Nextdeducesnextoccurringtimebasedontandunderlyingstates.
typeScheduleinterface{
Next(ttime.Time)time.Time
}

//JobistheinterfacethatwrapsthebasicRunmethod.
//
//Runexecutestheunderlyingfunc.
typeJobinterface{
Run()
}
  • Schedule 代表了一個(gè)【觸發(fā)器】,或者說(shuō)一個(gè)定時(shí)策略。它只包含一個(gè) Next 方法,接受一個(gè)時(shí)間點(diǎn),業(yè)務(wù)要返回下一次觸發(fā)調(diào)動(dòng)的時(shí)間點(diǎn)。

  • Job 則是對(duì)【任務(wù)】的抽象,只需要實(shí)現(xiàn)一個(gè) Run 方法,沒(méi)有入?yún)⒊鰠ⅰ?/p>

除了這兩個(gè)核心依賴(lài)外,Entry 結(jié)構(gòu)還包含了【前一次執(zhí)行時(shí)間點(diǎn)】和【下一次執(zhí)行時(shí)間點(diǎn)】,這個(gè)目前可以忽略,只是為了輔助代碼用。

按照時(shí)間排序

//byTimeisahandywrappertochronologicallysortentries.
typebyTime[]*Entry

func(bbyTime)Len()int{returnlen(b)}
func(bbyTime)Swap(i,jint){b[i],b[j]=b[j],b[i]}

//Lessreports`earliest`timeishouldsortbeforej.
//zerotimeisnot`earliest`time.
func(bbyTime)Less(i,jint)bool{

ifb[i].Next.IsZero(){
returnfalse
}
ifb[j].Next.IsZero(){
returntrue
}

returnb[i].Next.Before(b[j].Next)
}

這里是對(duì) Entry 列表的簡(jiǎn)單封裝,因?yàn)槲覀兛赡芡瑫r(shí)有多個(gè) Entry 需要調(diào)度,處理的順序很重要。這里實(shí)現(xiàn)了 sort 的接口, 有了 Len(), Swap(), Less() 我們就可以用 sort.Sort() 來(lái)排序了。

此處的排序策略是按照時(shí)間大小。

新增定時(shí)任務(wù)

我們?cè)谑纠锩娉霈F(xiàn)過(guò)調(diào)用 AddFunc() 來(lái)加入一個(gè) gron.Every(xxx) 這樣一個(gè)【定時(shí)任務(wù)】。其實(shí)這是給用戶(hù)提供的簡(jiǎn)單封裝。

//JobFuncisanadaptertoallowtheuseofordinaryfunctionsasgron.Job
//Iffisafunctionwiththeappropriatesignature,JobFunc(f)isahandler
//thatcallsf.
//
//todo:possiblyfuncwithparams?maybenotneeded.
typeJobFuncfunc()

//Runcallsj()
func(jJobFunc)Run(){
j()
}


//AddFuncregisterstheJobfunctionforthegivenSchedule.
func(c*Cron)AddFunc(sSchedule,jfunc()){
c.Add(s,JobFunc(j))
}

//Addappendsschedule,jobtoentries.
//
//ifcroninstantisnotrunning,addingtoentriesistrivial.
//otherwise,topreventdata-race,addsthroughchannel.
func(c*Cron)Add(sSchedule,jJob){

entry:=&Entry{
Schedule:s,
Job:j,
}

if!c.running{
c.entries=append(c.entries,entry)
return
}
c.add<-?entry
}

JobFunc 實(shí)現(xiàn)了我們上一節(jié)提到的 Job 接口,基于此,我們就可以讓用戶(hù)直接傳入一個(gè) func() 就ok,內(nèi)部轉(zhuǎn)成 JobFunc,再利用通用的 Add 方法將其加入到 Cron 中即可。

注意,這里的 Add 方法就是新增定時(shí)任務(wù)的核心能力了,我們需要觸發(fā)器 Schedule,任務(wù) Job。并以此來(lái)構(gòu)造出一個(gè)定時(shí)任務(wù) Entry。

若 Cron 實(shí)例還沒(méi)啟動(dòng),加入到 Cron 的 entries 列表里就ok,隨后啟動(dòng)的時(shí)候會(huì)處理。但如果已經(jīng)啟動(dòng)了,就直接往 add 這個(gè) channel 中塞,走額外的新增調(diào)度路徑。

啟動(dòng)和停止

//Startsignalscroninstantctogetupandrunning.
func(c*Cron)Start(){
c.running=true
goc.run()
}


//Stophaltscroninstantcfromrunning.
func(c*Cron)Stop(){

if!c.running{
return
}
c.running=false
c.stop<-?struct{}{}
}

我們先 high level 地看一下一個(gè) Cron 的啟動(dòng)和停止。

  • Start 方法執(zhí)行的時(shí)候會(huì)先將 running 變量置為 true,用來(lái)標(biāo)識(shí)實(shí)例已經(jīng)啟動(dòng)(啟動(dòng)前后加入的定時(shí)任務(wù) Entry 處理策略是不同的,所以這里需要標(biāo)識(shí)),然后啟動(dòng)一個(gè) goroutine 來(lái)實(shí)際跑啟動(dòng)的邏輯。

  • Stop 方法則會(huì)將 running 置為 false,然后直接往 stop channel 塞一個(gè)空結(jié)構(gòu)體即可。

ok,有了這個(gè)心里預(yù)期,我們來(lái)看看 c.run() 里面干了什么事:

varafter=time.After


//runthescheduler...
//
//Itneedstobeprivateasit'sresponsibleofsynchronizingacritical
//sharedstate:`running`.
func(c*Cron)run(){

vareffectivetime.Time
now:=time.Now().Local()

//tofigurenexttrigtimeforentries,referencedfromnow
for_,e:=rangec.entries{
e.Next=e.Schedule.Next(now)
}

for{
sort.Sort(byTime(c.entries))
iflen(c.entries)>0{
effective=c.entries[0].Next
}else{
effective=now.AddDate(15,0,0)//topreventphantomjobs.
}

select{
casenow=<-after(effective.Sub(now)):
???//entrieswithsametimegetsrun.
for_,entry:=rangec.entries{
ifentry.Next!=effective{
break
}
entry.Prev=now
entry.Next=entry.Schedule.Next(now)
goentry.Job.Run()
}
casee:=<-c.add:
???e.Next?=?e.Schedule.Next(time.Now())
???c.entries?=?append(c.entries,e)
case<-c.stop:
???return//terminatego-routine.
}
}
}

重點(diǎn)來(lái)了,看看我們是如何把上面 Cron, Entry, Schedule, Job 串起來(lái)的。

  • 首先拿到 local 的時(shí)間 now;
  • 遍歷所有 Entry,調(diào)用 Next 方法拿到各個(gè)【定時(shí)任務(wù)】下一次運(yùn)行的時(shí)間點(diǎn);
  • 對(duì)所有 Entry 按照時(shí)間排序(我們上面提過(guò)的 byTime);
  • 拿到第一個(gè)要到期的時(shí)間點(diǎn),在 select 里面通過(guò) time.After 來(lái)監(jiān)聽(tīng)。到點(diǎn)了就起動(dòng)新的 goroutine 跑對(duì)應(yīng) entry 里的 Job,并回到 for 循環(huán),繼續(xù)重新 sort,再走同樣的流程;
  • 若 add channel 里有新的 Entry 被加進(jìn)來(lái),就加入到 Cron 的 entries 里,觸發(fā)新的 sort;
  • 若 stop channel 收到了信號(hào),就直接 return,結(jié)束執(zhí)行。

整體實(shí)現(xiàn)還是非常簡(jiǎn)潔的,大家可以感受一下。

Schedule

前面其實(shí)我們暫時(shí)將觸發(fā)器的復(fù)雜性封裝在 Schedule 接口中了,但怎么樣實(shí)現(xiàn)一個(gè) Schedule 呢?

尤其是注意,我們還支持 At 操作,也就是指定 Day,和具體的小時(shí),分鐘?;貞浺幌拢?/p>

gron.Every(30*xtime.Day).At("00:00")
gron.Every(1*xtime.Week).At("23:59")

這一節(jié)我們就來(lái)看看,gron.Every 干了什么事,又是如何支持 At 方法的。

//EveryreturnsaSchedulereoccurseveryperiodp,pmustbeatleast
//time.Second.
funcEvery(ptime.Duration)AtSchedule{

ifp//truncatesuptoseconds

return&periodicSchedule{
period:p,
}
}

gron 的 Every 函數(shù)接受一個(gè) time.Duration,返回了一個(gè) AtSchedule 接口。我待會(huì)兒會(huì)看,這里注意,Every 里面是會(huì)把【秒】級(jí)以下給截掉。

我們先來(lái)看下,最后返回的這個(gè) periodicSchedule 是什么:

typeperiodicSchedulestruct{
periodtime.Duration
}

//Nextaddstimettounderlyingperiod,truncatesuptounitofseconds.
func(psperiodicSchedule)Next(ttime.Time)time.Time{
returnt.Truncate(time.Second).Add(ps.period)
}

//Atreturnsaschedulewhichreoccurseveryperiodp,attimet(hh:ss).
//
//Note:Atpanicswhenperiodpislessthanxtime.Day,anderrorhh:ssformat.
func(psperiodicSchedule)At(tstring)Schedule{
ifps.periodpanic("periodmustbeatleastindays")
}

//parsetnaively
h,m,err:=parse(t)

iferr!=nil{
panic(err.Error())
}

return&atSchedule{
period:ps.period,
hh:h,
mm:m,
}
}

//parsenaivelytokeniseshoursandminutes.
//
//returnserrorwheninputformatwasincorrect.
funcparse(hhmmstring)(hhint,mmint,errerror){

hh=int(hhmm[0]-'0')*10+int(hhmm[1]-'0')
mm=int(hhmm[3]-'0')*10+int(hhmm[4]-'0')

ifhh0||hh>24{
hh,mm=0,0
err=errors.New("invalidhhformat")
}
ifmm0||mm>59{
hh,mm=0,0
err=errors.New("invalidmmformat")
}

return
}

可以看到,所謂 periodicSchedule 就是一個(gè)【周期性觸發(fā)器】,只維護(hù)一個(gè) time.Duration 作為【周期】。

periodicSchedule 實(shí)現(xiàn) Next 的方式也很簡(jiǎn)單,把秒以下的截掉之后,直接 Add(period),把周期加到當(dāng)前的 time.Time 上,返回新的時(shí)間點(diǎn)。這個(gè)大家都能想到。

重點(diǎn)在于,對(duì) At 能力的支持。我們來(lái)關(guān)注下 func (ps periodicSchedule) At(t string) Schedule 這個(gè)方法

  • 若周期連 1 天都不到,不支持 At 能力,因?yàn)?At 本質(zhì)是在選定的一天內(nèi),指定小時(shí),分鐘,作為輔助。連一天都不到的周期,是要精準(zhǔn)處理的;

  • 將用戶(hù)輸入的形如 "23:59" 時(shí)間字符串解析出來(lái)【小時(shí)】和【分鐘】;

  • 構(gòu)建出一個(gè) atSchedule 對(duì)象,包含了【周期時(shí)長(zhǎng)】,【小時(shí)】,【分鐘】。

ok,這一步只是拿到了材料,那具體怎樣處理呢?這個(gè)還是得繼續(xù)往下走,看看 atSchedule 結(jié)構(gòu)干了什么:

typeatSchedulestruct{
periodtime.Duration
hhint
mmint
}

//resetreturnsnewDatebasedontimeinstantt,andreconfigureitshh:ss
//accordingtoatSchedule'shh:ss.
func(asatSchedule)reset(ttime.Time)time.Time{
returntime.Date(t.Year(),t.Month(),t.Day(),as.hh,as.mm,0,0,time.UTC)
}

//Nextreturns**next**time.
//iftpasseditssupposedschedule:reset(t),returnsreset(t)+period,
//elsereturnsreset(t).
func(asatSchedule)Next(ttime.Time)time.Time{
next:=as.reset(t)
ift.After(next){
returnnext.Add(as.period)
}
returnnext
}

其實(shí)只看這個(gè) Next 的實(shí)現(xiàn)即可。我們從 periodSchedule 那里獲取了三個(gè)屬性。

在調(diào)用 Next 方法時(shí),先做 reset,根據(jù)原有 time.Time 的年,月,日,以及用戶(hù)輸入的 At 中的小時(shí),分鐘,來(lái)構(gòu)建出來(lái)一個(gè) time.Time 作為新的時(shí)間點(diǎn)。

此后判斷是在哪個(gè)周期,如果當(dāng)前周期已經(jīng)過(guò)了,那就按照下個(gè)周期的時(shí)間點(diǎn)返回。

到這里,一切就都清楚了,如果我們不用 At 能力,直接 gron.Every(xxx),那么直接就會(huì)調(diào)用

t.Truncate(time.Second).Add(ps.period)

拿到一個(gè)新的時(shí)間點(diǎn)返回。

而如果我們要用 At 能力,指定當(dāng)天的小時(shí),分鐘。那就會(huì)走到 periodicSchedule.At 這里,解析出【小時(shí)】和【分鐘】,最后走 Next 返回 reset 之后的時(shí)間點(diǎn)。

這個(gè)和 gron.Every 方法返回的 AtSchedule 接口其實(shí)是完全對(duì)應(yīng)的:

//AtScheduleextendsSchedulebyenablingperiodic-interval&time-specificsetup
typeAtScheduleinterface{
At(tstring)Schedule
Schedule
}

直接就有一個(gè) Schedule 可以用,但如果你想針對(duì)天級(jí)以上的 duration 指定時(shí)間,也可以走 At 方法,也會(huì)返回一個(gè) Schedule 供我們使用。

擴(kuò)展性

gron 里面對(duì)于所有的依賴(lài)也都做成了【依賴(lài)接口而不是實(shí)現(xiàn)】。Cron 的 Add 函數(shù)的入?yún)⒁彩莾蓚€(gè)接口,這里可以隨意替換:func (c *Cron) Add(s Schedule, j Job)。

最核心的兩個(gè)實(shí)體依賴(lài) Schedule, Job 都可以用你自定義的實(shí)現(xiàn)來(lái)替換掉。

如實(shí)現(xiàn)一個(gè)新的 Job:

typeReminderstruct{
Msgstring
}

func(rReminder)Run(){
fmt.Println(r.Msg)
}

事實(shí)上,我們上面提到的 periodicSchedule 以及 atSchedule 就是 Schedule 接口的具體實(shí)現(xiàn)。我們也完全可以不用 gron.Every,而是自己寫(xiě)一套新的 Schedule 實(shí)現(xiàn)。只要實(shí)現(xiàn) Next(p time.Duration) time.Time 即可。

我們來(lái)看一個(gè)完整用法案例:

packagemain

import(
"fmt"
"github.com/roylee0704/gron"
"github.com/roylee0704/gron/xtime"
)

typePrintJobstruct{Msgstring}

func(pPrintJob)Run(){
fmt.Println(p.Msg)
}

funcmain(){

var(
//schedules
daily=gron.Every(1*xtime.Day)
weekly=gron.Every(1*xtime.Week)
monthly=gron.Every(30*xtime.Day)
yearly=gron.Every(365*xtime.Day)

//contrivedjobs
purgeTask=func(){fmt.Println("purgeagedrecords")}
printFoo=printJob{"Foo"}
printBar=printJob{"Bar"}
)

c:=gron.New()

c.Add(daily.At("12:30"),printFoo)
c.AddFunc(weekly,func(){fmt.Println("Everyweek")})
c.Start()

//JobsmayalsobeaddedtoarunningGron
c.Add(monthly,printBar)
c.AddFunc(yearly,purgeTask)

//StopGron(runningjobsarenothalted).
c.Stop()
}

經(jīng)典寫(xiě)法-控制退出

這里我們還是要聊一下 Cron 里控制退出的經(jīng)典寫(xiě)法。我們把其他不相關(guān)的部分清理掉,只留下核心代碼:

typeCronstruct{
stopchanstruct{}
}

func(c*Cron)Stop(){
c.stop<-?struct{}{}
}

func(c*Cron)run(){

for{
select{
case<-c.stop:
???return//terminatego-routine.
}
}
}

空結(jié)構(gòu)體能夠最大限度節(jié)省內(nèi)存,畢竟我們只是需要一個(gè)信號(hào)。核心邏輯用 for + select 的配合,這樣當(dāng)我們需要結(jié)束時(shí)可以立刻響應(yīng)。非常經(jīng)典,建議大家日常有需要的時(shí)候采用。

結(jié)語(yǔ)

gron 整體代碼其實(shí)只在 cron.go 和 schedule.go 兩個(gè)文件,合起來(lái)代碼不過(guò) 300 行,非常精巧,基本沒(méi)有冗余,擴(kuò)展性很好,是非常好的入門(mén)材料。

不過(guò),作為一個(gè) cron 的替代品,其實(shí) gron 還是有自己的問(wèn)題的。簡(jiǎn)單講就是,如果我重啟了一個(gè)EC2實(shí)例,那么我的 cron job 其實(shí)也還會(huì)繼續(xù)執(zhí)行,這是落盤(pán)的,操作系統(tǒng)級(jí)別的支持。

但如果我執(zhí)行 gron 的進(jìn)程掛掉了,不好意思,那就完全涼了。你只有重啟,然后再把所有任務(wù)加回來(lái)才行。而我們既然要用 gron,是很有可能定一個(gè)幾天后,幾個(gè)星期后,幾個(gè)月后這樣的觸發(fā)器的。誰(shuí)能保證進(jìn)程一直活著呢?連機(jī)子本身都可能重啟。

所以,我們需要一定的機(jī)制來(lái)保證 gron 任務(wù)的可恢復(fù)性,將任務(wù)落盤(pán),持久化狀態(tài)信息,算是個(gè)思考題,這里大家可以考慮一下怎么做。

審核編輯 :李倩



聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 操作系統(tǒng)
    +關(guān)注

    關(guān)注

    37

    文章

    7261

    瀏覽量

    127899
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4922

    瀏覽量

    72253

原文標(biāo)題:解析 Golang 定時(shí)任務(wù)庫(kù) gron 設(shè)計(jì)和原理

文章出處:【微信號(hào):LinuxHub,微信公眾號(hào):Linux愛(ài)好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    HTTP開(kāi)發(fā)必備:核心庫(kù)與httpplus擴(kuò)展庫(kù)應(yīng)用示例全攻略

    HTTP開(kāi)發(fā)的必備參考!本文匯總核心庫(kù)基礎(chǔ)操作與httpplus擴(kuò)展庫(kù)高級(jí)特性,通過(guò)示例解析,讓你快速上手各類(lèi)HTTP開(kāi)發(fā)需求。
    的頭像 發(fā)表于 09-20 15:19 ?2774次閱讀
    HTTP開(kāi)發(fā)必備:核心<b class='flag-5'>庫(kù)</b>與httpplus擴(kuò)展<b class='flag-5'>庫(kù)</b>應(yīng)用示例全攻略

    Crontab定時(shí)任務(wù)完全指南

    在凌晨3點(diǎn),當(dāng)大多數(shù)人還在熟睡時(shí),一位運(yùn)維工程師的手機(jī)突然響起——線(xiàn)上數(shù)據(jù)庫(kù)備份失敗了。他匆忙起床,打開(kāi)電腦,手動(dòng)執(zhí)行備份腳本,整個(gè)過(guò)程耗時(shí)2小時(shí)。這樣的場(chǎng)景,在我剛?cè)胄袝r(shí)經(jīng)常遇到。直到我真正掌握了crontab定時(shí)任務(wù),才徹底擺脫了"人肉運(yùn)維"的窘境。
    的頭像 發(fā)表于 09-05 10:03 ?378次閱讀

    Task任務(wù):LuatOS實(shí)現(xiàn)“任務(wù)級(jí)并發(fā)”的核心引擎

    LuatOS應(yīng)用程序運(yùn)行的核心大腦——所有LuatOS應(yīng)用項(xiàng)目都會(huì)使用到sys核心庫(kù)。 ? ?sys核心庫(kù)提供了四大類(lèi)功能: Task任務(wù) Message消息 Timer定時(shí)器 Run
    的頭像 發(fā)表于 08-28 13:49 ?240次閱讀
    Task<b class='flag-5'>任務(wù)</b>:LuatOS實(shí)現(xiàn)“<b class='flag-5'>任務(wù)</b>級(jí)并發(fā)”的核心引擎

    揭秘LuatOS Task:多任務(wù)管理的“智能中樞”

    ,也是LuatOS應(yīng)用程序運(yùn)行的核心大腦——所有LuatOS應(yīng)用項(xiàng)目都會(huì)使用到sys核心庫(kù)。 ? ?sys核心庫(kù)提供了四大類(lèi)功能: Task任務(wù) Message消息 Timer定時(shí)
    的頭像 發(fā)表于 08-28 13:48 ?270次閱讀
    揭秘LuatOS Task:多<b class='flag-5'>任務(wù)</b>管理的“智能中樞”

    【HZ-T536開(kāi)發(fā)板免費(fèi)體驗(yàn)】5、安裝sqlite3和使用golang讀寫(xiě)數(shù)據(jù)庫(kù)

    如果想在嵌入式設(shè)備上實(shí)現(xiàn)簡(jiǎn)單的設(shè)備管理功能,需要數(shù)據(jù)庫(kù)和服務(wù)后端程序。服務(wù)端程序,我更傾向使用golang來(lái)實(shí)現(xiàn)。 安裝sqlite3,使用ubuntu環(huán)境,可以直接用apt install安裝程序
    發(fā)表于 08-26 00:04

    使用C#實(shí)現(xiàn)西門(mén)子PLC數(shù)據(jù)定時(shí)讀取保存

    在平時(shí)開(kāi)發(fā)中,我們時(shí)常會(huì)遇到需要后臺(tái)靜默運(yùn)行的應(yīng)用場(chǎng)景,這些程序不需要用戶(hù)的直接操作或界面展示,而是專(zhuān)注于定時(shí)任務(wù)的執(zhí)行。比如說(shuō),我們需要定期從西門(mén)子PLC(可編程邏輯控制器)中讀取數(shù)據(jù)并進(jìn)行保存,以便后續(xù)分析使用。
    的頭像 發(fā)表于 08-07 16:17 ?1831次閱讀
    使用C#實(shí)現(xiàn)西門(mén)子PLC數(shù)據(jù)<b class='flag-5'>定時(shí)</b>讀取保存

    睿擎多核 SMP 開(kāi)發(fā):極簡(jiǎn)開(kāi)發(fā),超強(qiáng)性能——睿擎派開(kāi)發(fā)板0元試用

    在工業(yè)控制、邊緣計(jì)算等場(chǎng)景中,MPU多核架構(gòu)的性能潛力常因開(kāi)發(fā)復(fù)雜度難以釋放。實(shí)時(shí)任務(wù)(如運(yùn)動(dòng)控制、高速采集)與計(jì)算密集型任務(wù)(如UI交互、網(wǎng)絡(luò)通信、協(xié)議解析)混合運(yùn)行,導(dǎo)致以下問(wèn)題:實(shí)時(shí)性劣化
    的頭像 發(fā)表于 05-29 17:04 ?917次閱讀
    睿擎多核 SMP 開(kāi)發(fā):極簡(jiǎn)開(kāi)發(fā),超強(qiáng)性能——睿擎派開(kāi)發(fā)板0元試用

    快速入門(mén)——LuatOS:sys庫(kù)任務(wù)管理實(shí)戰(zhàn)攻略!

    的開(kāi)發(fā)者,這里將用最簡(jiǎn)明的步驟,助你輕松實(shí)現(xiàn)多任務(wù)應(yīng)用開(kāi)發(fā)! sys庫(kù)是LuatOS的核心系統(tǒng)調(diào)度庫(kù),它基于Lua協(xié)程機(jī)制實(shí)現(xiàn)了實(shí)時(shí)多任務(wù)調(diào)度、定時(shí)
    的頭像 發(fā)表于 05-29 14:36 ?457次閱讀
    快速入門(mén)——LuatOS:sys<b class='flag-5'>庫(kù)</b>多<b class='flag-5'>任務(wù)</b>管理實(shí)戰(zhàn)攻略!

    linux服務(wù)器挖礦病毒處理方案

    情況說(shuō)明:挖礦進(jìn)程被隱藏(CPU占用50%,htop/top卻看不到異常進(jìn)程),結(jié)束挖礦進(jìn)程后馬上又會(huì)運(yùn)行起來(lái)(crontab -l查看發(fā)現(xiàn)沒(méi)有定時(shí)任務(wù))。
    的頭像 發(fā)表于 04-09 10:33 ?781次閱讀
    linux服務(wù)器挖礦病毒處理方案

    【第四章 定時(shí)任務(wù)】手把手教你玩轉(zhuǎn)新版正點(diǎn)原子云

    【第四章 定時(shí)任務(wù)】手把手教你玩轉(zhuǎn)新版正點(diǎn)原子云 承接上篇,除了報(bào)警聯(lián)動(dòng)這個(gè)功能,原子云還有一個(gè)特色功能也是各開(kāi)發(fā)者喜歡用的,定時(shí)任務(wù)功能。 【正點(diǎn)原子】云平臺(tái):原子云(點(diǎn)擊登錄原子云) 前言
    發(fā)表于 03-13 10:19

    Linux計(jì)劃任務(wù)介紹

    點(diǎn)定時(shí)備份數(shù)據(jù)。比如:11點(diǎn)開(kāi)啟網(wǎng)站搶購(gòu)接口,12點(diǎn)關(guān)閉網(wǎng)站搶購(gòu)接口。 3.計(jì)劃任務(wù)主要分為以下兩種使用情況: 1.系統(tǒng)級(jí)別的定時(shí)任務(wù): 臨時(shí)文件清理、系統(tǒng)信息采集、日志文件切割?2.用戶(hù)級(jí)別的
    的頭像 發(fā)表于 11-24 15:49 ?978次閱讀

    Golang配置代理方法

    由于一些客觀原因的存在,我們開(kāi)發(fā) Golang 項(xiàng)目的過(guò)程總會(huì)碰到無(wú)法下載某些依賴(lài)包的問(wèn)題。這不是一個(gè)小問(wèn)題,因?yàn)槟愕墓ぷ鲿?huì)被打斷,即便你使用各種神通解決了問(wèn)題,很可能這時(shí)你的線(xiàn)程已經(jīng)切換到其他的事情上了(痛恨思路被打斷!)。所以最好是一開(kāi)始我們就重視這個(gè)問(wèn)題,并一勞永逸的解決它。
    的頭像 發(fā)表于 11-11 11:17 ?1485次閱讀
    <b class='flag-5'>Golang</b>配置代理方法

    定時(shí)器技術(shù):Air780E如何革新定時(shí)任務(wù)管理?

    今天講的是關(guān)于Air780E如何革新定時(shí)任務(wù)管理的內(nèi)容,希望大家有所收獲。
    的頭像 發(fā)表于 11-07 13:50 ?1040次閱讀
    <b class='flag-5'>定時(shí)</b>器技術(shù):Air780E如何革新<b class='flag-5'>定時(shí)任務(wù)</b>管理?

    mysql定時(shí)備份任務(wù)

    在生產(chǎn)環(huán)境上,為了避免數(shù)據(jù)的丟失,通常情況下都會(huì)定時(shí)的對(duì)數(shù)據(jù)庫(kù)進(jìn)行備份。而Linux的crontab指令則可以幫助我們實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)定時(shí)進(jìn)行備份。首先我們來(lái)簡(jiǎn)單了解crontab指令,如
    的頭像 發(fā)表于 10-31 10:07 ?751次閱讀

    Python庫(kù)解析:通過(guò)庫(kù)實(shí)現(xiàn)代理請(qǐng)求與數(shù)據(jù)抓取

    在Python中,有多個(gè)庫(kù)可以幫助你實(shí)現(xiàn)代理請(qǐng)求和數(shù)據(jù)抓取。這些庫(kù)提供了豐富的功能和靈活的API,使得你可以輕松地發(fā)送HTTP請(qǐng)求、處理響應(yīng)、解析HTML/XML/JSON數(shù)據(jù),以及進(jìn)行復(fù)雜的網(wǎng)絡(luò)操作。
    的頭像 發(fā)表于 10-24 07:54 ?662次閱讀