感覺豁然開朗,受益匪淺;
去繁就簡,再加上自己的一些理解,整理了一下
排程器
主要基於三個基本物件上,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的編譯工具鏈還是很強大的,十分有利於程式的優化。
如果有任何疑問,歡迎提出,
隨時更新
golang開發 二 安裝 Golang
當然了我們的安裝都是在vagrant裡面安裝,vagrant ssh。不用虛擬機器了,本機安裝當然也可以。go is a tool for managing go source code.usage go command arguments the commands are build compil...
golang開發 二 安裝 Golang
當然了我們的安裝都是在vagrant裡面安裝,vagrant ssh。不用虛擬機器了,本機安裝當然也可以。go is a tool for managing go source code.usage go command arguments the commands are build compil...
golang多核的使用
實際上協程只是發生在單個程序內部的,要是想充分的發掘多核cpu的潛力,還是需要多程序的支援。對於多核程式設計,go是天生支援,那麼我們在什麼情況下應該用多核心來加速程式,而在什麼情況下用單核即可呢?現在我們用一簡單的程式來說明下 定義任務佇列 varwaitgroup sync waitgroup ...