goroutine簡介
golang語言作者Rob Pike說,“Goroutine是一個與其他goroutines 並發運行在同一地址空間的Go函數或方法。一個運行的程序由一個或更多個goroutine組成。它與線程、協程、進程等不同。它是一個goroutine“。
- goroutine通過通道來通信,而協程通過讓出和恢復操作來通信;
- goroutine 通過Golang 的調度器進行調度,而協程通過程序本身調度;
簡單的說就是Golang自己實現了協程並叫做goruntine(本文稱Go協程),且比協程更強大。
goroutine調度原理
上面說到Go協程是通過Golang的調度器進行調度的,其中調度器的線程模型為兩級線程模型。
有關兩級線程模型的介紹,可以看這篇文章
我們來看下Golang實現的兩級線程模型是怎樣的。首先要知道這三個字母代表的含義
- M:代表內核級的線程
- P:全程Processor,代表運行Go協程所需要的資源(上下文環境)
- G:代表Go協程
我們先看下為實現調度Golang定義了這些數據結構存M,P,G
名稱 | 作用范圍 | 描述 |
---|---|---|
全局M列表 | Go的運行時 | 存放所有M的單向鏈表 |
全局P列表 | Go的運行時 | 存放所有P的數組 |
全局G列表 | Go的運行時 | 存放所有G的切片 |
調度器的空閑M列表 | 調度器 | 存放空閑M的單向鏈表 |
調度器的空閑P列表 | 調度器 | 存放空閑P的單向鏈表 |
調度器的自由G列表 | 調度器 | 存放自由G的單向鏈表(有兩個) |
調度器的可運行G隊列 | 調度器 | 存放可運行G的隊列 |
P的自由G列表 | 本地P | 存放當前P中自由G的單向鏈表 |
P的可運行G隊列 | 本地P | 存放當前P中可運行G的隊列 |
然后從上往下解析Go的兩級線程模型圖
(1)M和內核線程之間是一對一的關系,一個M在其生命周期中,只會和一個內核線程關聯,所以不會出現對內核線程的頻繁切換;
Golang的運行時執行系統監控和垃圾回收等任務時候會導致創建M,M空閑時不會被銷毀,而是放到一個
調度器的空閑M列表
中,等待與P關聯,M默認數量為10000
(2)P和M之間是多對多的關系,P和G之間是一對多的關系,他們的關聯是易變的,由Golang的調度器完成調度;
Golang的運行時按規則調度,讓P和不同的M建立或斷開關聯,使得P中的G能夠及時獲得運行時機
(3)P的數量默認為CPU總核心數,最大為256,當P沒有可運行的G時候(P的可運行G隊列為空),P會被放到調度器的空閑P列表
中,等待M與它關聯;
P有可能會被銷毀,如運行時用runtime.GOMAXPROCS把P的數量從32降到16時,剩余16個會被銷毀,它們原來的G會先轉到調度器
可運行的G隊列
和自由G列表
(4)每個P中有可運行的G隊列
(如圖中最下面的那行G)和自由G列表
(圖中未畫出來),當G的代碼執行完后,該G不會被銷毀,而是被放到P的自由G列表
或調度器的自由G列表
。如果程序新建了Go協程,調度器會在自由G列表中取一個G,然后把Go協程的函數賦值到G中(如果自由G列表為空,就創建一個G);
可見Golang調度器在調度時很大程度復用了M,P,G
(5)在Go程序初始化后,調度器首先進行一輪調度,此時用M去搜索可運行的G。其中我們的main函數也是一個G,找到可運行的G后就執行它;
至於怎么找可運行的G呢?答案是到處找,想盡辦法找(這里只列出一部分地方)。
- 從
本地P的可運行的G隊列
找- 從
調度器的可運行的G隊列
找- 從
其他P的可運行的G隊列
找
(6)P的可運行G隊列
最大只能存放長度為256的G,當隊列滿后,調度器會把一半的G轉到調度器的可運行G隊列
。
系統監控
上面大概描述了關於goroutine調度的流程。現在還存在一個問題,那就是當Go協程很多(並發量大)時候,顯然G是不能一直執行下去的,因為也需要把執行機會留給其他的G。此時Golang運行時的系統監控就起作用了。
一般情況,當G運行時間超過10ms后,該G就會被系統告知需要停止了,讓其他G運行。(這里情況比較復雜,並不能確保每個G都能被公平執行)
以下特殊情況該G不需要停止
- P的可運行G隊列為空(沒有其他G可運行)
- 有空閑的M在尋找可運行的G(沒有其他G可運行)
- 空閑的P(還有P閑着)
總結
Golang以兩級線程實現模型,自己實現goruntine和調度器,優勢在於並行和非常低的資源使用。
主要體現:
- 內存消耗方面(每個Go協程占的內存遠小於線程占的內存)
- 切換(調度)開銷方面
- 線程切換涉及模式切換(從用戶態切換到內核態)
此外,Go協程執行任務完成的順序並不都是按我們預期的那樣(程序不加以控制的情況下),特別在一些耗時較長的任務中。且每個Go協程執行的時間也不是絕對公平的。
如有錯誤地方,還請狂噴!