golang的goroutine調度機制

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

golang的goroutine調度機制

 

著作權聲明:本文為博主原創文章,未經博主允許不得轉載。

 

目錄(?)[-]

  1. 一直對goroutine的調度機制很好奇最近在看雨痕的golang源碼分析基於go14
  2. 這篇文章是去年整理的記錄公司內部wiki上
 

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

感覺豁然開朗,受益匪淺;

去繁就簡,再加上自己的一些理解,整理了一下

~~

調度器

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

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

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上~)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.