成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

嗯,你覺得 Go 在什么時候會搶占 P?

開發 后端
有新的小伙伴會產生更多的疑問,那就是在 Go 語言中,是如何搶占 P 的呢,這里面是怎么做的?今天這篇文章我們就來解密搶占 P。

 [[393948]]

本文轉載自微信公眾號「腦子進煎魚了」,作者陳煎魚。轉載本文請聯系腦子進煎魚了公眾號。

大家好,我是煎魚。

昨天剛從長沙浪完回來,準備 ”瀟瀟灑灑“ 寫一篇游記,分享一波美食+游記,有五一準備去長沙玩的小伙伴嗎?

前幾天我們有聊到《單核 CPU,開兩個 Goroutine,其中一個死循環,會怎么樣?》的問題,我們在一個細節部分有提到:

有新的小伙伴會產生更多的疑問,那就是在 Go 語言中,是如何搶占 P 的呢,這里面是怎么做的?

今天這篇文章我們就來解密搶占 P。

調度器的發展史

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

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

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

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

  • Go0.x:基于單線程的程調度器。
  • Go1.0:基于多線程的調度器。
  • Go1.1:基于任務竊取的調度器。
  • Go1.2 - Go1.13:基于協作的搶占式調度器。
  • Go1.14:基于信號的搶占式調度器。

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

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

為什么要搶占 P

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

這在調度器設計中顯然是不合理的。

跟這個例子一樣:

  1. // Main Goroutine  
  2. func main() { 
  3.     // 模擬單核 CPU 
  4.     runtime.GOMAXPROCS(1) 
  5.      
  6.     // 模擬 Goroutine 死循環 
  7.     go func() { 
  8.         for { 
  9.         } 
  10.     }() 
  11.  
  12.     time.Sleep(time.Millisecond) 
  13.     fmt.Println("腦子進煎魚了"

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

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

這其實沒有問題,因為該 Goroutine 已經阻塞在了系統調用上,暫時是不會有后續的執行新訴求。

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

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

  1. 若是有 P,則可以調整狀態,繼續運行。
  2. 若是沒有 P,可以重新搶 P,再占有并綁定 P,為自己所用。

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

怎么搶占 P

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

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

搶占阻塞在系統調用上的 P。

搶占運行時間過長的 G。

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

  1. func retake(now int64) uint32 { 
  2.  n := 0 
  3.  // 防止發生變更,對所有 P 加鎖 
  4.  lock(&allpLock) 
  5.  // 走入主邏輯,對所有 P 開始循環處理 
  6.  for i := 0; i < len(allp); i++ { 
  7.   _p_ := allp[i] 
  8.   pd := &_p_.sysmontick 
  9.   s := _p_.status 
  10.   sysretake := false 
  11.   ... 
  12.   if s == _Psyscall { 
  13.    // 判斷是否超過 1 個 sysmon tick 周期 
  14.    t := int64(_p_.syscalltick) 
  15.    if !sysretake && int64(pd.syscalltick) != t { 
  16.     pd.syscalltick = uint32(t) 
  17.     pd.syscallwhen = now 
  18.     continue 
  19.    } 
  20.        
  21.    ... 
  22.   } 
  23.  } 
  24.  unlock(&allpLock) 
  25.  return uint32(n) 

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

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

場景一

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

  1. t := int64(_p_.syscalltick) 
  2. if !sysretake && int64(pd.syscalltick) != t { 
  3.  pd.syscalltick = uint32(t) 
  4.  pd.syscallwhen = now 
  5.  continue 

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

場景二

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

  1. func retake(now int64) uint32 { 
  2.  for i := 0; i < len(allp); i++ { 
  3.   ... 
  4.   if s == _Psyscall { 
  5.    // 從此處開始分析 
  6.    if runqempty(_p_) &&  
  7.       atomic.Load(&sched.nmspinning)+atomic.Load(&sched.npidle) > 0 &&  
  8.       pd.syscallwhen+10*1000*1000 > now { 
  9.     continue 
  10.    } 
  11.    ... 
  12.   } 
  13.  } 
  14.  unlock(&allpLock) 
  15.  return uint32(n) 

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

  • runqempty(_p_) == true 方法會判斷任務隊列 P 是否為空,以此來檢測有沒有其他任務需要執行。
  • atomic.Load(&sched.nmspinning)+atomic.Load(&sched.npidle) > 0 會判斷是否存在空閑 P 和正在進行調度竊取 G 的 P。
  • pd.syscallwhen+10*1000*1000 > now 會判斷系統調用時間是否超過了 10ms。

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

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

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

  1. func retake(now int64) uint32 { 
  2.  for i := 0; i < len(allp); i++ { 
  3.   ... 
  4.   if s == _Psyscall { 
  5.    // 承接上半部分 
  6.    unlock(&allpLock) 
  7.    incidlelocked(-1) 
  8.    if atomic.Cas(&_p_.status, s, _Pidle) { 
  9.     if trace.enabled { 
  10.      traceGoSysBlock(_p_) 
  11.      traceProcStop(_p_) 
  12.     } 
  13.     n++ 
  14.     _p_.syscalltick++ 
  15.     handoffp(_p_) 
  16.    } 
  17.    incidlelocked(1) 
  18.    lock(&allpLock) 
  19.   } 
  20.  } 
  21.  unlock(&allpLock) 
  22.  return uint32(n) 

解鎖相關屬性:需要調用 unlock 方法解鎖 allpLock,從而實現獲取 sched.lock,以便繼續下一步。

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

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

搶奪 P 和調控 M:調用 handoffp 方法從系統調用或鎖定的 M 中搶奪 P,會由新的 M 接管這個 P。

總結

至此完成了搶占 P 的基本流程,我們可得出滿足以下條件:

如果存在系統調用超時:存在超過 1 個 sysmon tick 周期(至少 20us)的任務,則會從系統調用中搶占 P。

如果沒有空閑的 P:所有的 P 都已經與 M 綁定。需要搶占當前正處于系統調用之,而實際上系統調用并不需要的這個 P 的情況,會將其分配給其它 M 去調度其它 G。

如果 P 的運行隊列里面有等待運行的 G,為了保證 P 的本地隊列中的 G 得到及時調度。而自己本身的 P 又忙于系統調用,無暇管理。此時會尋找另外一個 M 來接管 P,從而實現繼續調度 G 的目的。

參考

NUMA-aware scheduler for Go

go-under-the-hood

深入解析 Go-搶占式調度

Go語言調度器源代碼情景分析

 

責任編輯:武曉燕 來源: 腦子進煎魚了
相關推薦

2024-10-29 08:52:01

Go協作式調度

2021-09-29 09:24:21

GCGo STW

2023-06-06 16:54:00

2015-03-02 14:44:48

AngularJS jQuery超越

2021-03-23 10:08:02

編程互聯網數據科學

2025-02-28 09:04:08

2023-02-01 15:49:51

人工智能AI

2020-05-12 11:25:50

MySQLES數據庫

2017-05-15 09:55:07

2015-07-08 15:55:01

NSStringcopystrong

2009-06-19 16:29:47

EJBXML

2019-04-16 13:27:36

隱私數據信息保護

2013-11-28 16:03:24

2012-09-24 10:20:39

JavaScriptJS

2022-05-19 10:27:34

機器學習人工智能

2024-08-05 01:22:16

2017-06-28 15:06:51

PythonLambda函數

2016-10-28 15:58:29

大數據就業成功率

2022-09-08 09:42:26

JavaScripMapObject

2021-02-03 10:23:59

Wi-Fi 7Wi-Fi6數據速率
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 午夜www | 精品国产黄a∨片高清在线 www.一级片 国产欧美日韩综合精品一区二区 | 在线一区二区国产 | 欧美日韩精品一区二区天天拍 | 欧美在线一级 | 韩日在线| 在线日韩 | 国产亚洲精品久久久优势 | 国产激情视频网 | 国产一区二区三区四区三区四 | 中文字幕视频在线 | 在线观看视频福利 | 国产精品毛片 | 一区二区三区免费 | 亚洲国产成人精品久久久国产成人一区 | 国产精品成人69xxx免费视频 | 亚卅毛片 | 看片一区 | 午夜精品 | 视频在线一区 | 国产乱精品一区二区三区 | 成人免费视频网站在线看 | av中文字幕在线观看 | 中文字幕欧美日韩 | 精品一区在线 | www亚洲精品 | 黄色国产 | 久久久国产亚洲精品 | 毛片视频免费观看 | 久草视频观看 | 视频在线观看一区二区 | 日韩欧美一二三区 | 日日干天天操 | 日韩中文在线视频 | 久草中文网 | 久久中文字幕在线 | 激情五月婷婷综合 | 国产激情一区二区三区 | 国产日产精品一区二区三区四区 | 中文av字幕 | 成人免费观看视频 |