Go語言GMP模型


進程、線程、協程

  • 進程:進程是系統進行資源分配的基本單位,有獨立的內存空間,單切換代價極高,進程間通信也比較麻煩
  • 線程:線程是CPU調度和分派的基本單位,線程依附於進程,與其他線程共享進程的資源,僅有自己的(程序計數器,一組寄存器的值,和棧),線程切換代價小(但是線程之間的切換可能會設計用戶態和內核態的切換),由於共享進程資源,所以線程之間通信比較方便。
  • 協程:協程是一種用戶態的輕量級線程,協程的調度完全由用戶控制,協程切換只需要保存和恢復任務的上下文,沒有內核的開銷。協程間通信也比較簡單(協程間本身是不可搶占的,由於操作系統的調度機制無法影響到它,因此一般存在用戶自定義的調度機制)(也可以這么說內核線程依然叫“線程(thread)”,用戶線程叫“協程(co-routine)".)
    線程的代價
  • 使用過C++/Java的同學應該都知道在做服務端開發的時候,對於多線程的使用,繁瑣程度,以及對於多線程情況下異常情況的分析,是何等頭疼,如果線上流量過大,對於這種多線程的操作更是得慎重,當然對於機器資源的消耗也是隨之增加的,其實對於程序員來說最主要就是簡單,高效~
  1. 空間大小:一般情況下一個thread的創建大小默認占用棧空間1M,而goroutine默認則是2kb
  2. 切換開銷:thread的切換是需要通過用戶態達到內核態,會設計到上下文的切換開銷,對應資源的耗費會很大
  3. 線程通信:常見的線程通信有三種方式(C++),使用起來相對比較復雜,最棘手的問題就是共享內存,一般會使用鎖,使用到鎖,就會引申各種鎖的問題,比如死鎖。
  4. 資源回收:創建線程相對簡單,回收線程資源會麻煩很多,常見有detached和join倆種方式,對於無法大量創建線程的時候,會考慮多路復用方式,這樣的麻煩就是另個層次了。

GMP模型

  • G:Goroutine,每個Gotoutine對應一個G結構體,G存儲Goroutine的運行堆棧,狀態,以及任務函數,可重用(函數實體)G需要保存到P才能被調度執行
  • M:machine,os內核線程抽象,代表真正執行計算的資源,在綁定有效的P后,進入schedule循環;而shcedule循環的機制大致是從Global隊列,P的local隊列以及wait隊列中獲取。
    M的數量是不固定的,有Go Runtime調整,為了防止創建過多OS線程導致系統調度不過來,目前默認設置為10000個,M不保存G的上下文,這是G可以跨M的基礎。
  • P:Processor,表示邏輯處理器,對G來說,P相當於CPU核,G只有綁定到P才能被調度。對M來說,P提供了相關的執行環境,入內存分配狀態,任務隊列等。
    P 的數量決定了系統內最大可並行的 G 的數量(前提:物理 CPU 核數 >= P 的數量)。
    P 的數量由用戶設置的 GoMAXPROCS 決定,但是不論 GoMAXPROCS 設置為多大,P 的數量最大為 256。
  • 調度器循環的機制大致是從各種隊列、P 的本地隊列中獲取 G,切換到 G 的執行棧上並執行 G 的函數,調用 Goexit 做清理工作並回到 M,如此反復。
    可以通過經典的地鼠推車搬磚的模型來說明其三者關系:
  • 地鼠(Gopher)的工作任務是:工地上有若干磚頭,地鼠借助小車把磚頭運送到火種上去燒制。M 就可以看作圖中的地鼠,P 就是小車,G 就是小車里裝的磚。

Processor(P):
根據用戶設置的 GoMAXPROCS 值來創建一批小車(P)。
Goroutine(G):
通過 Go 關鍵字就是用來創建一個 Goroutine,也就相當於制造一塊磚(G),然后將這塊磚(G)放入當前這輛小車(P)中。
Machine (M):
地鼠(M)不能通過外部創建出來,只能磚(G)太多了,地鼠(M)又太少了,實在忙不過來,剛好還有空閑的小車(P)沒有使用,那就從別處再借些地鼠(M)過來直到把小車(P)用完為止。
這里有一個地鼠(M)不夠用,從別處借地鼠(M)的過程,這個過程就是創建一個內核線程(M)。
需要注意的是:地鼠(M) 如果沒有小車(P)是沒辦法運磚的,小車(P)的數量決定了能夠干活的地鼠(M)數量,在 Go 程序里面對應的是活動線程數;

調度過程

• 首先創建一個G對象,G對象保存到P本地隊列或者是全局隊列。P此時去喚醒一個M。P繼續執行它的執行序。M尋找是否有空閑的P,如果有則將該G對象移動到它本身。接下來M執行一個調度循環(調用G對象->執行->清理線程→繼續找新的Goroutine執行)。
• M執行過程中,隨時會發生上下文切換。當發生上線文切換時,需要對執行現場進行保護,以便下次被調度執行時進行現場恢復。Go調度器M的棧保存在G對象上,只需要將M所需要的寄存器(SP、PC等)保存到G對象上就可以實現現場保護。當這些寄存器數據被保護起來,就隨時可以做上下文切換了,在中斷之前把現場保存起來。如果此時G任務還沒有執行完,M可以將任務重新丟到P的任務隊列,等待下一次被調度執行。當再次被調度執行時,M通過訪問G的vdsoSP、vdsoPC寄存器進行現場恢復(從上次中斷位置繼續執行)。
參考
https://www.it610.com/article/1293176199840866304.htm
https://studygolang.com/articles/27934?fr=sidebar


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM