[轉]golang的goroutine調度機制

golang的goroutine調度機制linux

 

 
 

一直對goroutine的調度機制很好奇,最近在看雨痕的golang源碼分析,(基於go1.4)

感受豁然開朗,受益不淺;api

去繁就簡,再加上本身的一些理解,整理了一下數組

~~函數

調度器工具

主要基於三個基本對象上,G,M,P(定義在源碼的src/runtime/runtime.h文件中)源碼分析

1.     G表明一個goroutine對象,每次go調用的時候,都會建立一個G對象post

2.     M表明一個線程,每次建立一個M的時候,都會有一個底層線程建立;全部的G任務,最終仍是在M上執行優化

3.     P表明一個處理器,每個運行的M都必須綁定一個P,就像線程必須在麼一個CPU核上執行同樣

P的個數就是GOMAXPROCS(最大256),啓動時固定的,通常不修改; M的個數和P的個數不必定同樣多(會有休眠的M或者不須要太多的M)(最大10000);每個P保存着本地G任務隊列,也有一個全局G任務隊列;

以下圖所示

全局G任務隊列會和各個本地G任務隊列按照必定的策略互相交換(滿了,則把本地隊列的一半送給全局隊列)

P是用一個全局數組(255)來保存的,而且維護着一個全局的P空閒鏈表

每次go調用的時候,都會:

1.     建立一個G對象,加入到本地隊列或者全局隊列

2.     若是還有空閒的P,則建立一個M

3.     M會啓動一個底層線程,循環執行能找到的G任務

4.     G任務的執行順序是,先從本地隊列找,本地沒有則從全局隊列找(一次性轉移(全局G個數/P個數)個,再去其它P中找(一次性轉移一半),

5.     以上的G任務執行是按照隊列順序(也就是go調用的順序)執行的。(這個地方是否是以爲很奇怪??)

對於上面的第2-3步,建立一個M,其過程:

1.     先找到一個空閒的P,若是沒有則直接返回,(哈哈,這個地方就保證了進程不會佔用超過本身設定的cpu個數)

2.     調用系統api建立線程,不一樣的操做系統,調用不同,其實就是和c語言建立過程是一致的,(windows用的是CreateThread,linux用的是clone系統調用),(*^__^*)嘻嘻……

3.     而後建立的這個線程裏面纔是真正作事的,循環執行G任務

那就會有個問題,若是一個系統調用或者G任務執行太長,他就會一直佔用這個線程,因爲本地隊列的G任務是順序執行的,其它G任務就會阻塞了,怎樣停止長任務的呢?(這個地方我找了很久~o(╯□╰)o)

這樣滴,啓動的時候,會專門建立一個線程sysmon,用來監控和管理,在內部是一個循環:

1.     記錄全部P的G任務計數schedtick,(schedtick會在每執行一個G任務後遞增)

2.     若是檢查到 schedtick一直沒有遞增,說明這個P一直在執行同一個G任務,若是超過必定的時間(10ms),就在這個G任務的棧信息裏面加一個標記

3.     而後這個G任務在執行的時候,若是遇到非內聯函數調用,就會檢查一次這個標記,而後中斷本身,把本身加到隊列末尾,執行下一個G

4.     O(∩_∩)O哈哈~,若是沒有遇到非內聯函數(有時候正常的小函數會被優化成內聯函數)調用的話,那就慘了,會一直執行這個G任務,直到它本身結束;若是是個死循環,而且GOMAXPROCS=1的話,恭喜你,夯住了!親測,的確如此

對於一個G任務,中斷後的恢復過程:

1.     中斷的時候將寄存器裏的棧信息,保存到本身的G對象裏面

2.     當再次輪到本身執行時,將本身保存的棧信息複製到寄存器裏面,這樣就接着上次以後運行了。 ~\(≧▽≦)/~

 

可是還有一個問題,就是系統啓動的過程,雨痕沒有說的太明白,我一直有不少問題都狠疑惑(第一個M怎麼來的?,G怎麼找到對應的P?等等),這個讓我蛋疼了很久~

不過我本身意淫了一下,補充在下面,歡迎你們指正

1.     系統啓動的時候,首先跑的是主線程,那第一個M應該就是主線程吧(按照C語言的理解,嘿嘿),這裏叫M1,能夠看前面的圖

2.     而後這個主線程會綁定第一個P1

3.     我們寫的main函數,實際上是做爲一個goroutine來執行的(雨痕說的)

4.     也就是第一個P1就有了一個G1任務,而後第一個M1就執行這個G1任務(也就是main函數),建立這個G1的時候不用建立M了,由於已經有了M1

5.     這個main函數裏面全部的goroutine,都綁定到當前的M1所對應的P1上,O(∩_∩)O哈哈~

6.     而後建立main裏的goroutine的時候(好比G2),就會建立新的M2,新的M2裏的初始P2的本地任務隊列是空的,會從P1裏面取一些過來,哈哈

7.     這樣兩個M1,M2各自執行本身的G任務,再依次往復,這下就圓滿了~~~

 

綜上:

因此goroutine是按照搶佔式調度的,一個goroutine最多執行10ms就會換做下一個

這個和目前主流系統的的cpu調度相似(按照時間分片)

windows:20ms

linux:5ms-800ms


到這裏都差很少了,這些在雨痕的筆記裏面都有更詳細的描述,不過不少地方比較凌亂,比較複雜,這裏篩檢了不少,方便讀者理解

 

注意:

1.     在Golang中編譯器也會嘗試進行內聯,將小函數直接複製並編譯,爲了內聯,儘可能消除編譯器沒法偵測的dead code,利用gobuild -gcflags=-m編譯命令能夠查看程序內聯狀態,不得不說golang的編譯工具鏈仍是很強大的,十分有利於程序的優化。

 

若是有任何疑問,歡迎提出,

隨時更新

 

 

(這篇文章是去年整理的,記錄公司內部wiki上~)

相關文章
相關標籤/搜索