談談對 GMP 的簡單認識

語言: CN / TW / HK

theme: scrolls-light

猶記得最開始學習 golang 的時候,大佬們分享 GMP 模型的時候,總感覺雲裏霧裏,聽了半天,並沒有一個很清晰的概念,不知 xmd 是否會有這樣的體會

雖然 golang 入門很簡單,但是對於理解 golang 的設計思想和原理,還是需要一定時間的積累和沉澱,更多的應該是思想上的沉澱

希望這篇文章能夠對你瞭解 golang 的 GMP 模型有一點幫助

文章分別從一下三個方面來談談我對 GMP 模型認識

  • golang 中調度器的變化及其作用
  • 有了進程,線程,為什麼會出現協程
  • GMP 模型中的 G,M,P 分別都做着什麼樣的事情

golang 中的調度器的變化及其作用

調度器,scheduler

怎麼理解呢?調度器就像是一個管理者,負責安排事項,負責調度不同人在指定時間在某個崗位上完成自己的價值交付

正如 linux 調度器一樣,將就緒的進程調度成執行狀態,或者將執行狀態的進程,打斷,變成阻塞狀態,再變成就緒狀態

比如説一個經典的單進程 和 多進程/多線程的操作系統,

我們可以看到在單進程系統中,只需要無腦的將進程串行排列好, CPU 會串行去執行任務,如果遇到進程1 阻塞的情況,其他進程也沒有辦法被 cpu 執行,那麼進程2 ,進程3 ,進程4 就都要等待前面的進程完成執行完畢,才能到自己執行

可以看出單進程對於 CPU 的使用過於任性,浪費 CPU 的資源,演進到多進程/多線程操作系統的時候,就出現了調度器

上圖中我們可以對比看到,在多進程/多線程的操作系統中,cpu 的時間片被分割的更加的小,對於 cpu 資源的利用率是大大的增加了,因為 cpu 可以在進程1阻塞的時候,切換去執行進程2

例如,當進程1 執行過程中,發生了阻塞,那麼調度器就會就會將 cpu 切換到進程2 中進行執行,同理,進程2 阻塞的時候, cpu 就會被切換到進程 3 進行執行,當然,這就看是哪個進程先搶到 cpu 資源了

可以看到,調度器在這裏的作用就是最大限度的利用上 CPU 的資源,管理進程在 CPU 上按照一定的的順序執行任務,就好比一個優秀的管家可以合理安排好不同的員工在指定的時間上專注的處理某項事務

那麼 golang 的調度器是不是也是和 linux 中的調度器有着想通之處呢?

來看看調度器在 golang 中的具體作用是幹啥的

在 golang 中,調度器的實現簡單來看實際上是由協程和線程按照一定的邏輯來組合起來的,其實也是扮演着一個協調和調度的作用,調度的對象是協程和線程,協程是需要被調度到線程中來運行的,這個動作就是調度器乾的

  • 通常用 G 來表示協程
  • 用 M 來表示線程

正如是這樣來實現調度器的:

我們可以看到,當每一個 M 想要從全局隊列中取 G 出來執行的時候,是需要訪問全局隊列的鎖,這是一把互斥鎖,同一時間只能有 1 個 M 在訪問

Golang 的調度器此處當然也能讓多個 M 處理不同的 G,達到多進程/多線程 併發處理事項的目的

可我們知道,加鎖,是會影響到我們系統的整體性能的,畢竟那麼多 M 都在競爭這一把鎖,勢必同一時間沒有搶到的鎖的 M 就要等待了

為什麼調度器會被淘汰掉?

看到上圖,細心的我們可以發現,

所以,這個調度器缺點也是比較明顯的:

第一,多個 M 都要併發的去獲取全局隊列中的 G ,會造成鎖競爭 , 此處操作全局隊列的情況有如下幾種情況

  • 創建 G
  • 調度 G
  • 銷燬 G

第二, 線程 M 在執行 G 的過程中,如果 G 阻塞了,那麼 M 也就阻塞了,這個時候,CPU 就需要切換到其他的 M 上執行 G,這個切換的過程也是對於 CPU 資源的浪費

正如人的大腦,同時處理多個事項,當 事項 1 未處理完畢,就去處理事項 2,那麼需要重新理清思緒去執行,若這個時候又需要切換到 事項 1 的時候,就需要花更多的時間來回顧之前事項 1 處理的上下文了, 所以在工作中,沒有特別必要,不要打擾別人

第三, 我們知道在 golang 中,一個 G 也是可以再創建 G 的,就叫 G1 吧,那麼創建的這個 G1 就需要交給其他的 M 來進行執行了,因為當前的 M 正在執行 G,自己忙不過來,因此需要將 G1 交給 M1 執行

這也是一個很明顯的問題

正如上述例子一樣, 2 個人排查相關問題,但是信息和認知不一致,很明顯是小豬可能是比較難排查出問題來的

就像 M 執行 G 新創建了 G1,但是是交給 M1 來執行的, M1 並不知道 G1 和 G 的關係,M1 執行起來就可能會出現問題,例如需要 G 的一些上下文,但是 M1 並不知情

因此,隨着時間的推移,對於性能的要求是越來越高,當然是要想辦法換一個與時俱進,需要符合時代潮流的管家了,自然就出現了一個新的調度器替代了原有的調度器

出現新的調度器,自然是解決了舊的調度器的缺點,並且還帶來了一些新的屬性和價值,具體的新調度器策略我們可以在下文中進行展示

為什麼會出現協程

在來看另外一個問題,為什麼會出現協程,自然是因為使用進程和線程不能夠滿足我們的某些需求了,此處的需求是指對於性能的要求,是對 CPU 利用效率的需求

上圖中我們有説到,對於多進程/多線程併發的時候,我們有提高 CPU 的利用率,儘可能的利用好 CPU 的時間片,但是對於 CPU 從 進程/線程1 切換到去執行進程/線程 2 的過程中,是會產生消耗的,但是這個消耗很難避免

那麼我們知道 CPU 其實執行的是線程,那麼如果 1 個線程裏面還可以分成多個程序進行併發豈不是可以大大的提高我們當前線程的使用效率?

我們從 C/C++ 中知道,咱們的一個32位系統的機器,進程實際上是開闢了一個 4G 的虛擬空間,具體 4G 虛擬空間都包含了什麼,我們可以簡單的看看這個圖,本次不在此細聊

那線程大概也是需要 4M 的空間,那麼我們在一定程度上大量的開闢進程或者線程,必然會帶來系統中 內存佔用高,調度 CPU 切換的消耗高 的問題

後來,我們知道線程實際上是分為內核態和用户態的

當然,用户態的線程是依賴於內核態線程的,用户態中需要執行的內容,是需要放在內核態線程上進行執行的,另外, CPU 只知道有內核態線程的存在,意味着,CPU 只認內核態線程

此處説的內核態,和用户態,其實就是對應到我們説的 M 和 G

  • 內核態線程 -- 線程
  • 用户態線程 -- 協程

協程不會陷入到內核態中,因此在 M 不變的情況下,切換 G 就是非常輕快的了,協程簡要有如下特點:

  • 佔用內存空間小,只佔用 幾 kb ,比起進程,線程來説,真的是很小了
  • 調度靈活,他是處於用户態進行調度的

根據協程和線程處於的用户態和內核態,我們可以看到調度的機制是不一樣的,

內核態中的線程,實際上是搶佔式的,是又 CPU 調度的

用户態中的線程,即協程,是由用户態調度的,此處的用户態調度,就可以理解是一個隊列了,只能一個一個的去執行了,一個協程執行完畢之後,讓出 CPU ,才會執行下一個協程

好比我們代碼中,多個協程,其中 1 個協程 panic 了,如何不捕獲的話,是不是整個程序都崩潰了

對於 CPU 只認內核態線程這一點,咱理解起來就想到與一個公司的老闆,只認每個產品線上的總監一樣,你這個主管後面不管有多人少人在幹活,老闆只認為是你這個主管在幹活

當然一個公司也不僅僅只有 1 個老闆,就像計算機系統裏面也會有多個 CPU ,但是道理是一樣的

如何理解 GMP 模型

GMP 分別表示 協程 goroutine,線程 thread,處理器 processor

他們三組成了新的調度器,他們三者的關係這樣的

咱們從圖中可以知道有全局隊列,本地隊列

全局隊列還是和之前一樣,從裏面裏面 G 的創建,調度,銷燬 都是需要訪問互斥鎖的

P 的本地隊列 自己維護了一個 G 列表,最長是 256 個那 P 的個數一般是 GOMAXPROCS 個,如果本地的隊列滿了,那麼 P 會將隊列中的一半給到全局隊列中

從本地隊列中取 G 是不需要加鎖的,直接取即可,且如果隊列中的 G 又創建了 G1 ,那麼這個 G1 也是會被優先加入到當前本地隊列的

此處我們可以看到 M 是和 P 進行對應了,當 M 需要運行 G 的時候,就需要先找到一個合適的 P ,從 P 中獲取 G, 如果 P 為空,那麼 M 就會從全局隊列中獲取一批 G 放到 P 的隊列中

或者是 M 也會從其他的 P 的隊列中偷一半的 G 到當前 P 的隊列中

從這裏我們可以知道,新的調度器,已經解決了舊的調度器的一部分問題了(不需要每次都去找全局隊列)

那麼 P 是啥時候創建,創建多少個?M 又是啥時候創建的,創建多少個呢? P 和 M 在數量上有必然聯繫嗎?

P 是在程序啟動的時候,讀取環境變量中 GOMAXPROC ,來創建具體 P 的個數

M 的創建實際上是,若 P 中有很多任務 G ,如果有空閒的 M,那麼 P 就會找任意空閒 M 來進行處理,如果沒有空閒的 M ,那麼調度器就會去創建 M 來執行 P 中的 任務

那麼新的調度器還有哪些優勢呢?

第一

提高了線程的複用率,如果當前線程執行完當前 P 的任務之後,當前的 M 會嘗試去偷其他 P 裏面的 G 來進行執行,這樣我們就儘可能的避免了線程的創建,銷燬帶來的開銷

第二

如果當前的 M 在執行 G 的時候,出現了阻塞,那麼 M 也會很懂事的讓出當前的 P,讓其他 M 來執行 P 中的任務當前的 M 就繼續處理當前的 G

第三

上述我們説到當前 M 會去從其他 P 的隊列中偷 G,這個是在當前 M 對應的 P 中沒有 G 的時候,優先去做的事情, 如果其他的 P 也沒有 G 的時候,當前 M 才會去全局隊列中拿

從這裏就可以開始,新的調度器,已經是在大大的弱化了全局隊列的作用

本次先聊到這裏,相信你對 GMP 的基本理論也有一些瞭解了吧

感謝閲讀,歡迎交流,點個贊,關注一波 再走吧

我正在參與掘金技術社區創作者簽約計劃招募活動,點擊鏈接報名投稿

\