Go在什么時候會搶占P

本篇內(nèi)容主要講解“Go在什么時候會搶占P”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Go在什么時候會搶占P”吧!

創(chuàng)新互聯(lián)是一家專業(yè)提供丹鳳企業(yè)網(wǎng)站建設,專注與成都網(wǎng)站建設、網(wǎng)站制作、H5高端網(wǎng)站建設、小程序制作等業(yè)務。10年已為丹鳳眾多企業(yè)、政府機構(gòu)等服務。創(chuàng)新互聯(lián)專業(yè)網(wǎng)站設計公司優(yōu)惠進行中。

 Go在什么時候會搶占P

調(diào)度器的發(fā)展史

在 Go 語言中,Goroutine 早期是沒有設計成搶占式的,早期 Goroutine 只有讀寫、主動讓出、鎖等操作時才會觸發(fā)調(diào)度切換。

這樣有一個嚴重的問題,就是垃圾回收器進行 STW 時,如果有一個 Goroutine  一直都在阻塞調(diào)用,垃圾回收器就會一直等待他,不知道等到什么時候...

這種情況下就需要搶占式調(diào)度來解決問題。如果一個 Goroutine 運行時間過久,就需要進行搶占來解決。

這塊 Go 語言在 Go1.2 起開始實現(xiàn)搶占式調(diào)度器,不斷完善直至今日:

  • Go0.x:基于單線程的程調(diào)度器。

  • Go1.0:基于多線程的調(diào)度器。

  • Go1.1:基于任務竊取的調(diào)度器。

  • Go1.2 - Go1.13:基于協(xié)作的搶占式調(diào)度器。

  • Go1.14:基于信號的搶占式調(diào)度器。

調(diào)度器的新提案:非均勻存儲器訪問調(diào)度(Non-uniform memory access,NUMA),  但由于實現(xiàn)過于復雜,優(yōu)先級也不夠高,因此遲遲未提上日程。

有興趣的小伙伴可以詳見 Dmitry Vyukov, dvyukov 所提出的 NUMA-aware scheduler for Go。

為什么要搶占 P

為什么會要想去搶占 P 呢,說白了就是不搶,就沒機會運行,會 hang 死。又或是資源分配不均了,

這在調(diào)度器設計中顯然是不合理的。

跟這個例子一樣:

// Main Goroutine  func main() {     // 模擬單核 CPU     runtime.GOMAXPROCS(1)          // 模擬 Goroutine 死循環(huán)     go func() {         for {         }     }()      time.Sleep(time.Millisecond)     fmt.Println("腦子進煎魚了") }

這個例子在老版本的 Go 語言中,就會一直阻塞,沒法重見天日,是一個需要做搶占的場景。

但可能會有小伙伴問,搶占了,會不會有新問題。因為原本正在使用 P 的 M 就涼涼了(M 會與 P 進行綁定),沒了 P 也就沒法繼續(xù)執(zhí)行了。

這其實沒有問題,因為該 Goroutine 已經(jīng)阻塞在了系統(tǒng)調(diào)用上,暫時是不會有后續(xù)的執(zhí)行新訴求。

但萬一代碼是在運行了好一段時間后又能夠運行了(業(yè)務上也允許長等待),也就是該 Goroutine 從阻塞狀態(tài)中恢復了,期望繼續(xù)運行,沒了 P  怎么辦?

這時候該 Goroutine 可以和其他 Goroutine 一樣,先檢查自身所在的 M 是否仍然綁定著 P:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. 若是有 P,則可以調(diào)整狀態(tài),繼續(xù)運行。

  3. 若是沒有 P,可以重新?lián)?P,再占有并綁定 P,為自己所用。

也就是搶占 P,本身就是一個雙向行為,你搶了我的 P,我也可以去搶別人的 P 來繼續(xù)運行。

怎么搶占 P

講解了為什么要搶占 P 的原因后,我們進一步深挖,“他” 是怎么搶占到具體的 P 的呢?

這就涉及到前文所提到的 runtime.retake 方法了,其處理以下兩種場景:

搶占阻塞在系統(tǒng)調(diào)用上的 P。

搶占運行時間過長的 G。

在此主要針對搶占 P 的場景,分析如下:

func retake(now int64) uint32 {  n := 0  // 防止發(fā)生變更,對所有 P 加鎖  lock(&allpLock)  // 走入主邏輯,對所有 P 開始循環(huán)處理  for i := 0; i < len(allp); i++ {   _p_ := allp[i]   pd := &_p_.sysmontick   s := _p_.status   sysretake := false   ...   if s == _Psyscall {    // 判斷是否超過 1 個 sysmon tick 周期    t := int64(_p_.syscalltick)    if !sysretake && int64(pd.syscalltick) != t {     pd.syscalltick = uint32(t)     pd.syscallwhen = now     continue    }           ...   }  }  unlock(&allpLock)  return uint32(n) }

該方法會先對 allpLock 上鎖,這個變量含義如其名,allpLock 可以防止該數(shù)組發(fā)生變化。

其會保護 allp、idlepMask 和 timerpMask 屬性的無 P 讀取和大小變化,以及對 allp  的所有寫入操作,可以避免影響后續(xù)的操作。

場景一

前置處理完畢后,進入主邏輯,會使用萬能的 for 循環(huán)對所有的 P(allp)進行一個個處理。

t := int64(_p_.syscalltick) if !sysretake && int64(pd.syscalltick) != t {  pd.syscalltick = uint32(t)  pd.syscallwhen = now  continue }

第一個場景是:會對 syscalltick 進行判定,如果在系統(tǒng)調(diào)用(syscall)中存在超過 1 個 sysmon tick 周期(至少  20us)的任務,則會從系統(tǒng)調(diào)用中搶占 P,否則跳過。

場景二

如果未滿足會繼續(xù)往下,走到如下邏輯:

func retake(now int64) uint32 {  for i := 0; i < len(allp); i++ {   ...   if s == _Psyscall {    // 從此處開始分析    if runqempty(_p_) &&        atomic.Load(&sched.nmspinning)+atomic.Load(&sched.npidle) > 0 &&        pd.syscallwhen+10*1000*1000 > now {     continue    }    ...   }  }  unlock(&allpLock)  return uint32(n) }

第二個場景,聚焦到這一長串的判斷中:

  • runqempty(_p_) == true 方法會判斷任務隊列 P 是否為空,以此來檢測有沒有其他任務需要執(zhí)行。

  • atomic.Load(&sched.nmspinning)+atomic.Load(&sched.npidle) > 0  會判斷是否存在空閑 P 和正在進行調(diào)度竊取 G 的 P。

  • pd.syscallwhen+10*1000*1000 > now 會判斷系統(tǒng)調(diào)用時間是否超過了 10ms。

這里奇怪的是 runqempty 方法明明已經(jīng)判斷了沒有其他任務,這就代表了沒有任務需要執(zhí)行,是不需要搶奪 P 的。

但實際情況是,由于可能會阻止 sysmon 線程的深度睡眠,最終還是希望繼續(xù)占有 P。

在完成上述判斷后,進入到搶奪 P 的階段:

func retake(now int64) uint32 {  for i := 0; i < len(allp); i++ {   ...   if s == _Psyscall {    // 承接上半部分    unlock(&allpLock)    incidlelocked(-1)    if atomic.Cas(&_p_.status, s, _Pidle) {     if trace.enabled {      traceGoSysBlock(_p_)      traceProcStop(_p_)     }     n++     _p_.syscalltick++     handoffp(_p_)    }    incidlelocked(1)    lock(&allpLock)   }  }  unlock(&allpLock)  return uint32(n) }

解鎖相關(guān)屬性:需要調(diào)用 unlock 方法解鎖 allpLock,從而實現(xiàn)獲取 sched.lock,以便繼續(xù)下一步。

減少閑置 M:需要在原子操作(CAS)之前減少閑置 M 的數(shù)量(假設有一個正在運行)。否則在發(fā)生搶奪 M 時可能會退出系統(tǒng)調(diào)用,遞增 nmidle  并報告死鎖事件。

修改 P 狀態(tài):調(diào)用 atomic.Cas 方法將所搶奪的 P 狀態(tài)設為 idle,以便于交于其他 M 使用。

搶奪 P 和調(diào)控 M:調(diào)用 handoffp 方法從系統(tǒng)調(diào)用或鎖定的 M 中搶奪 P,會由新的 M 接管這個 P。

到此,相信大家對“Go在什么時候會搶占P”有了更深的了解,不妨來實際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學習!

文章題目:Go在什么時候會搶占P
網(wǎng)頁鏈接:http://muchs.cn/article18/ghgpdp.html

成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站改版、動態(tài)網(wǎng)站微信公眾號、手機網(wǎng)站建設、靜態(tài)網(wǎng)站、網(wǎng)站營銷

廣告

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

成都網(wǎng)頁設計公司