關於golang的goroutine scheduler
前言
G:指的是Goroutine
M:工作執行緒或機器
P:處理器,用來執行Go程式碼的資源
每個M需要有一個關聯P來執行Go程式碼,
當前scheduler的問題
當前goroutine scheduler限制使用go語言編寫的併發程式可擴充套件性,特別是,高吞吐服務和平行計算程式方面。Vtocc服務在8核機器上CPU高達70%,而profile顯示14%是花費在runtime.futex()這個地方。通常來說scheduler可能會禁止使用者在效能優關的地方使用慣用細粒度的併發。
當前實現存在的問題
- 單一的全域性互斥鎖(Sched.Lock)和中心式狀態,全域性互斥鎖保護了所有goroutine的關聯操作(建立、實現、重新排程等)
2、Goroutine並不干涉其他的goroutine(G.nextG).同樣工作執行緒經常也不干涉runnable goroutine在彼此之間,而這些可能會導致增加延遲和帶來額外花費。每個M能夠執行任意執行中的G,實際上M僅僅建立G。
3、每個M記憶體快取(M.mcache),與所有M關聯的記憶體快取和其他快取(比如堆分配記憶體),僅僅實在M執行go程式碼時才需要被分配(M在系統呼叫阻塞中是不需要分配記憶體快取的),在執行Go程式碼的M和所有M的記憶體比例最高達到1:100.這也導致資源浪費(每個MCache分配大小達到2M)及糟糕的資料存放位置
4、頻繁的blocking和unblocking,在系統呼叫時,工作執行緒會頻繁阻塞和非阻塞,會帶來額外的花費
scheduler 設計
Processors(處理器)
基本的思想是在執行引入P(Processors)的概念,並實現在Processor上實現搶奪式工作排程,而M表示作業系統執行緒,P表示執行Go程式碼時所需要的資源,當M執行Go程式碼時, 它會有對應關聯的P。在M空閒或在進行系統呼叫時,是需要P的。GOMAXPROCES即為P,所有的P都會被存放到一個array中,這也是搶奪式工作模式的要求,對應的GOMAXPROCS的改變會帶來停止/啟動這個GC World來重新調整P的陣列大小。在sched中的一些變數被分散並移到P中,M中的一些變數被移到P中,這些變數一般是與go程式碼活動執行有關的。
struct P{
Lock;
G *gfree; // freelist, moved from sched
G *ghead;// runnable, moved from sched
G *gtail;
MCache *mcache; // moved from M
FixAlloc *stackalloc; // moved from M
uint64 ncgocall;
GCStats gcstates;
// etc
...
};
P *allp; // [GOMAXPROCS]
p *idlep; // lock-free list of idle P
說明:當一個M將要執行Go程式碼,必須從無鎖空閒列表獲取一個P。而當M完成Go程式碼執行,就需要將執行GO程式碼的P放入到該列表中,以便後續的操作使用。因此在M執行Go程式碼時,都必有一個與之關聯的P。這種機制也取代了sched.atomic(mcpu/mcpumax)
排程scheduling
當新建一個G或已有的G重新執行,那麼該G則將被推到當前P的執行goroutine列表中。在該P完成執行G,P則嘗試從自己本地的執行goroutines列表pop一個G;若是本地列表為空,該P則會隨機選擇一個其他的P,並從選擇的P本地執行goroutine列表獲取一半的goroutine,這樣就提升P資源的利用,提升效率。
系統呼叫/M parking和unparking
在M建立一個新的g時,在所有的M都不是很忙的情況下,必須能夠確保有另外一個M來執行這個G。同樣,當一個M正處於系統呼叫時,也必須確保有另外一個M來執行Go程式碼。
這裡有兩種選擇,可以立即鎖定或解鎖M,也使用旋轉。這裡會存在必然的衝突在效能和消耗不必要的CPU時鐘週期之間。通過使用旋轉和消耗一定CPU時鐘週期來實現,然而對於GOMAXPROCS=1的程式(命令列程式、app引擎等)是沒有影響的。
關於旋轉有兩中型別:
(1)、一個關聯P的空閒M通過自旋查詢新的G
(2)、一個關聯P的w/o M等待可用的P
型別(1)的空閒M在有型別(2)空閒M時並不會阻塞/鎖定。常見的GOMAXPROCS旋轉M基本上就在兩種型別中。在一個新的G處於旋轉狀態或M在進行系統呼叫時甚至M從空閒轉變為忙碌,都能確保至少有一個M是旋轉的(或所有的P都處於忙碌狀態),同時這也確保了沒有runnable的G以其他方式執行,並且也避免了同一時間過多的M進行block或unblock。一般來說旋轉多半都是被動是由os呼叫sched_yield()來觸發,但也可能存在些主動自旋轉(迴圈消耗CPU),這些是需要研究並進行調優的。
終止/死鎖檢查
在分散式系統中,終止/死鎖檢查存在很多的不確定性。通常的想法是通過是在所有的P都處於空閒狀態下(全域性空閒P原子計數器),這允許進行更昂貴的檢查涉及每個P狀態的聚合。
LockOSThread
該功能並不是效能的關鍵
1、處於Locked的G則是不能進行runnable(狀態=Gwaiting),M立即將P返回到空閒列表(idle-list),並喚醒其他的M並阻塞
2、當處於Locked的G變成runnable並處於執行佇列的頭部,當前的M並不干涉自己對應的P和Locked G到Locked G相關的M上,並釋放,最終當前M變為idle
空閒G
該功能也不是效能的關鍵
這裡有一個全域性的空閒G佇列,一個M在多次嘗試失敗之後來檢查該佇列。
實現計劃
最終的目標通過將整個專案分割成能夠獨立評估和提交的最小單元。
1、引入結構體P(目前為空),實現allp/idlep容器(針對初學者來說idlep是一個互斥-受保護的);分配一個P給對應的M來執行Go程式碼,全域性的互斥和原子狀態是保持不變的
2、移動G freelist給P
3、移動M到P
4、移動堆分配到P
5、移動ncgocall/gsstats到P
6、分散執行佇列並實現搶奪式工作模式,清除G狀態轉換,仍處於全域性互斥下
7、移除全域性互斥,實現分散式終止檢查:LockOSThread
8、實現旋轉而不是blocking、unblocking
(該計劃有可能會失敗,有很多未被探索的細節)
進一步改善
1、嘗試LIFO排程,將提高區域性效能。然而,它仍然必須提供一定程度的公平性和優雅地處理生成goroutines
2、不要在goroutine首次執行時分配對應的G和stack;針對一個新建一個新的goroutine只需要callerpc、fn、narg、nret、args等6個命令。這也允許建立一些執行到完成的goroutine來顯著的降低記憶體的佔用
3、更好區域性G-P過程嘗試確保一個unblock的G到上次執行它的P上
4、更好的區域性P-M嘗試執行P在上次執行相同的M上
5、M建立限制,由於scheduler很容易每秒鐘建立數千個M,直到OS拒絕建立更多的執行緒。M就必須立即建立直至k * GOMAXPROCS,在這以後新建M都是通過計數器來完成新增的
後記
GOMAXPROCS不會因為這些調整而消失。
相關文章
- Go--關於 goroutine、channelGo
- [Golang基礎]GoroutineGolang
- Golang 的 goroutine 是如何實現的?Golang
- 第 12 期 golang 中 goroutine 的排程Golang
- Golang —— goroutine(協程)和channel(管道)Golang
- Golang 獲取 goroutine id 完全指南Golang
- Golang語言並行設計的核心goroutineGolang並行
- Golang 入門 : 等待 goroutine 完成任務Golang
- 「Golang成長之路」併發之GoroutineGolang
- 關於golang的time包總結Golang
- golang 原始碼分析之scheduler排程器Golang原始碼
- Golang併發程式設計——goroutine、channel、syncGolang程式設計
- 說說Golang goroutine併發那些事兒Golang
- golang 利用 WaitGroup 控制多個 goroutine 同時完成GolangAI
- Golang協程無法固定goroutine的最大數目解決Golang
- 關於Golang中的依賴注入實現Golang依賴注入
- 關於golang隨機種子的注意點Golang隨機
- golang pprof 監控系列(4) —— goroutine thread 統計原理Golangthread
- Golang-goroutine02(MPG模式+設定CPU數目)Golang模式
- Golang原始碼學習:排程邏輯(二)main goroutine的建立Golang原始碼AI
- golang中關於死鎖的思考與學習Golang
- Golang - 關於 proto 檔案的一點小思考Golang
- 關於Golang struct{}{}用法和注意事項GolangStruct
- 初學 GoLang 遇到的一個關於時間的坑...Golang
- 基於多 goroutine 實現令牌桶Go
- Golang語言goroutine協程併發安全及鎖機制Golang
- 兄弟連golang神技(1)-關於 Go 語言的介紹Golang
- goroutineGo
- Golang併發程式設計優勢與核心goroutine及注意細節Golang程式設計
- Quartz框架中的Schedulerquartz框架
- 需要提醒你關於 golang 中 map 使用的幾點注意事項Golang
- 基於golang的聊天室Golang
- 基於Golang的微服務——ConsulGolang微服務
- Reactor中的Thread和SchedulerReactthread
- goroutine間的同步&協作Go
- 深入解析Scheduler
- react之schedulerReact
- 大話 goroutineGo