欧美一区二区三区老妇人-欧美做爰猛烈大尺度电-99久久夜色精品国产亚洲a-亚洲福利视频一区二区

Go定時(shí)器內(nèi)部的實(shí)現(xiàn)原理是什么-創(chuàng)新互聯(lián)

這篇文章主要講解了“Go定時(shí)器內(nèi)部的實(shí)現(xiàn)原理是什么”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“Go定時(shí)器內(nèi)部的實(shí)現(xiàn)原理是什么”吧!

寬城網(wǎng)站制作公司哪家好,找成都創(chuàng)新互聯(lián)!從網(wǎng)頁設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、APP開發(fā)、響應(yīng)式網(wǎng)站開發(fā)等網(wǎng)站項(xiàng)目制作,到程序開發(fā),運(yùn)營維護(hù)。成都創(chuàng)新互聯(lián)成立與2013年到現(xiàn)在10年的時(shí)間,我們擁有了豐富的建站經(jīng)驗(yàn)和運(yùn)維經(jīng)驗(yàn),來保證我們的工作的順利進(jìn)行。專注于網(wǎng)站建設(shè)就選成都創(chuàng)新互聯(lián)。

前言

本節(jié),我們重點(diǎn)關(guān)注系統(tǒng)協(xié)程是如何管理這些定器的,包括以下問題:

定時(shí)器使用什么數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)?定時(shí)器如何觸發(fā)事件?定時(shí)器如何添加進(jìn)系統(tǒng)協(xié)程?定時(shí)器如何從系統(tǒng)協(xié)程中刪除?


定時(shí)器存儲(chǔ)

timer數(shù)據(jù)結(jié)構(gòu)

Timer和Ticker數(shù)據(jù)結(jié)構(gòu)除名字外完全一樣,二者都含有一個(gè)runtimeTimer類型的成員,這個(gè)就是系統(tǒng)協(xié)程所維護(hù)的對(duì)象。runtimeTimer類型是time包的名稱,在runtime包中,這個(gè)類型叫做timer。

timer數(shù)據(jù)結(jié)構(gòu)如下所示:

type timer struct {    tb *timersBucket // the bucket the timer lives in   // 當(dāng)前定時(shí)器寄存于系統(tǒng)timer堆的地址 i int // heap index                      // 當(dāng)前定時(shí)器寄存于系統(tǒng)timer堆的下標(biāo) when   int64 // 當(dāng)前定時(shí)器下次觸發(fā)時(shí)間 period int64 // 當(dāng)前定時(shí)器周期觸發(fā)間隔(如果是Timer,間隔為0,表示不重復(fù)觸發(fā)) f func(interface{}, uintptr) // 定時(shí)器觸發(fā)時(shí)執(zhí)行的函數(shù) arg interface{} // 定時(shí)器觸發(fā)時(shí)執(zhí)行函數(shù)傳遞的參數(shù)一 seq    uintptr // 定時(shí)器觸發(fā)時(shí)執(zhí)行函數(shù)傳遞的參數(shù)二(該參數(shù)只在網(wǎng)絡(luò)收發(fā)場(chǎng)景下使用) }

其中timersBucket便是系統(tǒng)協(xié)程存儲(chǔ)timer的容器,里面有個(gè)切片來存儲(chǔ)timer,而i便是timer所在切片的下標(biāo)。

timersBucket數(shù)據(jù)結(jié)構(gòu)

我們來看一下timersBucket數(shù)據(jù)結(jié)構(gòu):

type timersBucket struct { lock         mutexgp           *g // 處理堆中事件的協(xié)程 created bool // 事件處理協(xié)程是否已創(chuàng)建,默認(rèn)為false,添加首個(gè)定時(shí)器時(shí)置為true sleeping bool // 事件處理協(xié)程(gp)是否在睡眠(如果t中有定時(shí)器,還未到觸發(fā)的時(shí)間,那么gp會(huì)投入睡眠) rescheduling bool // 事件處理協(xié)程(gp)是否已暫停(如果t中定時(shí)器均已刪除,那么gp會(huì)暫停) sleepUntil   int64 // 事件處理協(xié)程睡眠時(shí)間 waitnote     note // 事件處理協(xié)程睡眠事件(據(jù)此喚醒協(xié)程) t            []*timer // 定時(shí)器切片 }

“Bucket”譯成中文意為"桶",顧名思義,timersBucket意為存儲(chǔ)timer的容器。

lock: 互斥鎖,在timer增加和刪除時(shí)需要使用;gp: 事件處理協(xié)程,就是我們所說的系統(tǒng)協(xié)程,這個(gè)協(xié)程在首次創(chuàng)建Timer或Ticker時(shí)生成;create: 狀態(tài)值,表示系統(tǒng)協(xié)程是否創(chuàng)建;sleeping: 系統(tǒng)協(xié)程是否在睡眠;rescheduling: 系統(tǒng)協(xié)程是否已暫停;sleepUntil: 系統(tǒng)協(xié)程睡眠到指定的時(shí)間(如果有新的定時(shí)任務(wù)可能會(huì)提前喚醒);waitnote: 提前喚醒時(shí)使用的通知;t: 保存timer的切片,當(dāng)調(diào)用NewTimer()或NewTicker()時(shí)便會(huì)有新的timer存到此切片中;

看到這里應(yīng)該能明白,系統(tǒng)協(xié)程在首次創(chuàng)建定時(shí)器時(shí)創(chuàng)建,定時(shí)器存儲(chǔ)在切片中,系統(tǒng)協(xié)程負(fù)責(zé)計(jì)時(shí)并維護(hù)這個(gè)切片。

存儲(chǔ)拓?fù)?/h3>

以Ticker為例,我們回顧一下Ticker、timer和timersBucket關(guān)系,假設(shè)我們已經(jīng)創(chuàng)建了3個(gè)Ticker,那么它們之間的關(guān)系如下:

用戶創(chuàng)建Ticker時(shí)會(huì)生成一個(gè)timer,這個(gè)timer指向timersBucket,timersBucket記錄timer的指針。

timersBucket數(shù)組

通過timersBucket數(shù)據(jù)結(jié)構(gòu)可以看到,系統(tǒng)協(xié)程負(fù)責(zé)計(jì)時(shí)并維護(hù)其中的多個(gè)timer,一個(gè)timersBucket包含一個(gè)系統(tǒng)協(xié)程。

當(dāng)系統(tǒng)中定時(shí)器非常多時(shí),一個(gè)系統(tǒng)協(xié)程可能處理能力跟不上,所以Go在實(shí)現(xiàn)時(shí)實(shí)際上提供了多個(gè)timersBucket,也就有多個(gè)系統(tǒng)協(xié)程來處理定時(shí)器。

最理想的情況,應(yīng)該預(yù)留GOMAXPROCS個(gè)timersBucket,以便充分使用CPU資源,但需要跟據(jù)實(shí)際環(huán)境動(dòng)態(tài)分配。為了實(shí)現(xiàn)簡單,Go在實(shí)現(xiàn)時(shí)預(yù)留了64個(gè)timersBucket,絕大部分場(chǎng)景下這些足夠了。

每當(dāng)協(xié)程創(chuàng)建定時(shí)器時(shí),使用協(xié)程所屬的ProcessID%64來計(jì)算定時(shí)器存入的timersBucket。

下圖三個(gè)協(xié)程創(chuàng)建定時(shí)器時(shí),定時(shí)器分布如下圖所示:

為描述方便,上圖中3個(gè)協(xié)程均分布于3個(gè)Process中。

一般情況下,同一個(gè)Process的協(xié)程創(chuàng)建的定時(shí)器分布于同一個(gè)timersBucket中,只有當(dāng)GOMAXPROCS大于64時(shí)才會(huì)出現(xiàn)多個(gè)Process分布于同一個(gè)timersBucket中。

定時(shí)器運(yùn)行機(jī)制

看完上面的數(shù)據(jù)結(jié)構(gòu),了解了timer是如何存儲(chǔ)的?,F(xiàn)在開始探究定時(shí)器內(nèi)部運(yùn)作機(jī)制。

創(chuàng)建定時(shí)器

回顧一下定時(shí)器創(chuàng)建過程,創(chuàng)建Timer或Ticker實(shí)際上分為兩步:

創(chuàng)建一個(gè)管道創(chuàng)建一個(gè)timer并啟動(dòng)(注意此timer不是Timer,而是系統(tǒng)協(xié)程所管理的timer。)

創(chuàng)建管道的部分前面已做過介紹,這里我們重點(diǎn)關(guān)注timer的啟動(dòng)部分。

首先,每個(gè)timer都必須要?dú)w屬于某個(gè)timersBucket的,所以第一步是先選擇一個(gè)timersBucket,選擇的算法很簡單,將當(dāng)前協(xié)程所屬的Processor ID 與timersBucket數(shù)組長度求模,結(jié)果就是timersBucket數(shù)組的下標(biāo)。

const timersLen = 64 var timers [timersLen]struct { // timersBucket數(shù)組,長度為64 timersBucket}func (t *timer) assignBucket() *timersBucket { id := uint8(getg().m.p.ptr().id) % timersLen // Processor ID 與數(shù)組長度求模,得到下標(biāo) t.tb = &timers[id].timersBucket return t.tb}

至此,第一步,給當(dāng)前的timer選擇一個(gè)timersBucket已經(jīng)完成。

其次,每個(gè)timer都必須要加入到timersBucket中。前面我們知道,timersBucket中切片中保存著timer的指針,新加入的timer并不是按加入時(shí)間順序存儲(chǔ)的,而是把timer按照觸發(fā)的時(shí)間排序的一個(gè)小頭堆。那么timer加入timersBucket的過程實(shí)際上也是堆排序的過程,只不過這個(gè)排序是指的是新加元素后的堆調(diào)整過程。

源碼src/runtime/time.go:addtimerLocked()函數(shù)負(fù)責(zé)添加timer:

func (tb *timersBucket) addtimerLocked(t *timer) bool { if t.when < 0 {t.when = 1<<63 - 1 }t.i = len(tb.t) // 先把定時(shí)器插入到堆尾 tb.t = append(tb.t, t) // 保存定時(shí)器 if !siftupTimer(tb.t, t.i) { // 堆中插入數(shù)據(jù),觸發(fā)堆重新排序 return false } if t.i == 0 { // 堆排序后,發(fā)現(xiàn)新插入的定時(shí)器跑到了棧頂,需要喚醒協(xié)程來處理 // siftup moved to top: new earliest deadline. if tb.sleeping { // 協(xié)程在睡眠,喚醒協(xié)程來處理新加入的定時(shí)器 tb.sleeping = false notewakeup(&tb.waitnote)} if tb.rescheduling { // 協(xié)程已暫停,喚醒協(xié)程來處理新加入的定時(shí)器 tb.rescheduling = false goready(tb.gp, 0)}} if !tb.created { // 如果是系統(tǒng)首個(gè)定時(shí)器,則啟動(dòng)協(xié)程處理堆中的定時(shí)器 tb.created = true go timerproc(tb)}return true }

跟據(jù)注釋來理解上面的代碼比較簡單,這里附加幾點(diǎn)說明:

如果timer的時(shí)間是負(fù)值,那么會(huì)被修改為很大的值,來保證后續(xù)定時(shí)算法的正確性;系統(tǒng)協(xié)程是在首次添加timer時(shí)創(chuàng)建的,并不是一直存在;新加入timer后,如果新的timer跑到了棧頂,意味著新的timer需要立即處理,那么會(huì)喚醒系統(tǒng)協(xié)程。

下圖展示一個(gè)小頂堆結(jié)構(gòu),圖中每個(gè)圓圈代表一個(gè)timer,圓圈中的數(shù)字代表距離觸發(fā)事件的秒數(shù),圓圈外的數(shù)字代表其在切片中的下標(biāo)。其中timer 15是新加入的,加入后它被最終調(diào)整到數(shù)組的1號(hào)下標(biāo)。

上圖展示的是二叉堆,實(shí)際上Go實(shí)現(xiàn)時(shí)使用的是四叉堆,使用四叉堆的好處是堆的高度降低,堆調(diào)整時(shí)更快。

刪除定時(shí)器

當(dāng)Timer執(zhí)行結(jié)束或Ticker調(diào)用Stop()時(shí)會(huì)觸發(fā)定時(shí)器的刪除。從timersBucket中刪除定時(shí)器是添加定時(shí)器的逆過程,即堆中元素刪除后,觸發(fā)堆調(diào)整。在此不再細(xì)述。

timerproc

timerproc為系統(tǒng)協(xié)程的具體實(shí)現(xiàn)。它是在首次創(chuàng)建定時(shí)器創(chuàng)建并啟動(dòng)的,一旦啟動(dòng)永不銷毀。 如果timersBucket中有定時(shí)器,取出堆頂定時(shí)器,計(jì)算睡眠時(shí)間,然后進(jìn)入睡眠,醒來后觸發(fā)事件。

某個(gè)timer的事件觸發(fā)后,跟據(jù)其是否是周期性定時(shí)器來決定將其刪除還是修改時(shí)間后重新加入堆。

如果堆中已沒有事件需要觸發(fā),則系統(tǒng)協(xié)程將進(jìn)入暫停態(tài),也可認(rèn)為是無限時(shí)睡眠,直到有新的timer加入才會(huì)被喚醒。

timerproc處理事件的流程圖如下:

資源泄露問題

前面介紹Ticker時(shí)格外提醒不使用的Ticker需要顯式的Stop(),否則會(huì)產(chǎn)生資源泄露。研究過timer實(shí)現(xiàn)機(jī)制后,可以很好的解釋這個(gè)問題了。

首先,創(chuàng)建Ticker的協(xié)程并不負(fù)責(zé)計(jì)時(shí),只負(fù)責(zé)從Ticker的管道中獲取事件; 其次,系統(tǒng)協(xié)程只負(fù)責(zé)定時(shí)器計(jì)時(shí),向管道中發(fā)送事件,并不關(guān)心上層協(xié)程如何處理事件;

如果創(chuàng)建了Ticker,則系統(tǒng)協(xié)程將持續(xù)監(jiān)控該Ticker的timer,定期觸發(fā)事件。如果Ticker不再使用且沒有Stop(),那么系統(tǒng)協(xié)程負(fù)擔(dān)會(huì)越來越重,最終將消耗大量的CPU資源。

感謝各位的閱讀,以上就是“Go定時(shí)器內(nèi)部的實(shí)現(xiàn)原理是什么”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對(duì)Go定時(shí)器內(nèi)部的實(shí)現(xiàn)原理是什么這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián)網(wǎng)站建設(shè)公司,,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

本文題目:Go定時(shí)器內(nèi)部的實(shí)現(xiàn)原理是什么-創(chuàng)新互聯(lián)
URL標(biāo)題:http://chinadenli.net/article20/deeoco.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站收錄、靜態(tài)網(wǎng)站、面包屑導(dǎo)航、響應(yīng)式網(wǎng)站全網(wǎng)營銷推廣、網(wǎng)站維護(hù)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

成都做網(wǎng)站