## 調度器調度場景過程全解析
(1) 場景 1
P 擁有 G1,M1 獲取 P 后開始運行 G1,G1 使用 go func() 創建了 G2,為了局部性 G2 優先加入到 P1 的本地隊列。

(2) 場景 2
G1 運行完成后 (函數:goexit),M 上運行的 goroutine 切換為 G0,G0 負責調度時協程的切換(函數:schedule)。從 P 的本地隊列取 G2,從 G0 切換到 G2,并開始運行 G2 (函數:execute)。實現了線程 M1 的復用。

(3) 場景 3
假設每個 P 的本地隊列只能存 3 個 G。G2 要創建了 6 個 G,前 3 個 G(G3, G4, G5)已經加入 p1 的本地隊列,p1 本地隊列滿了。

(4) 場景 4
G2 在創建 G7 的時候,發現 P1 的本地隊列已滿,需要執行負載均衡 (把 P1 中本地隊列中前一半的 G,還有新創建 G 轉移到全局隊列)
> (實現中并不一定是新的 G,如果 G 是 G2 之后就執行的,會被保存在本地隊列,利用某個老的 G 替換新 G 加入全局隊列)

這些 G 被轉移到全局隊列時,會被打亂順序。所以 G3,G4,G7 被轉移到全局隊列。
(5) 場景 5
G2 創建 G8 時,P1 的本地隊列未滿,所以 G8 會被加入到 P1 的本地隊列。

G8 加入到 P1 點本地隊列的原因還是因為 P1 此時在與 M1 綁定,而 G2 此時是 M1 在執行。所以 G2 創建的新的 G 會優先放置到自己的 M 綁定的 P 上。
(6) 場景 6
規定:在創建 G 時,運行的 G 會嘗試喚醒其他空閑的 P 和 M 組合去執行。

假定 G2 喚醒了 M2,M2 綁定了 P2,并運行 G0,但 P2 本地隊列沒有 G,M2 此時為自旋線程(沒有 G 但為運行狀態的線程,不斷尋找 G)。
(7) 場景 7
M2 嘗試從全局隊列 (簡稱 “GQ”) 取一批 G 放到 P2 的本地隊列(函數:findrunnable())。M2 從全局隊列取的 G 數量符合下面的公式:
> n = min(len(GQ)/GOMAXPROCS + 1, len(GQ/2))
至少從全局隊列取 1 個 g,但每次不要從全局隊列移動太多的 g 到 p 本地隊列,給其他 p 留點。這是從全局隊列到 P 本地隊列的負載均衡。

假定我們場景中一共有 4 個 P(GOMAXPROCS 設置為 4,那么我們允許最多就能用 4 個 P 來供 M 使用)。所以 M2 只從能從全局隊列取 1 個 G(即 G3)移動 P2 本地隊列,然后完成從 G0 到 G3 的切換,運行 G3。
(8) 場景 8
假設 G2 一直在 M1 上運行,經過 2 輪后,M2 已經把 G7、G4 從全局隊列獲取到了 P2 的本地隊列并完成運行,全局隊列和 P2 的本地隊列都空了,如場景 8 圖的左半部分。

全局隊列已經沒有 G,那 m 就要執行 work stealing (偷取):從其他有 G 的 P 哪里偷取一半 G 過來,放到自己的 P 本地隊列。P2 從 P1 的本地隊列尾部取一半的 G,本例中一半則只有 1 個 G8,放到 P2 的本地隊列并執行。
(9) 場景 9
G1 本地隊列 G5、G6 已經被其他 M 偷走并運行完成,當前 M1 和 M2 分別在運行 G2 和 G8,M3 和 M4 沒有 goroutine 可以運行,M3 和 M4 處于自旋狀態,它們不斷尋找 goroutine。

為什么要讓 m3 和 m4 自旋,自旋本質是在運行,線程在運行卻沒有執行 G,就變成了浪費 CPU. 為什么不銷毀現場,來節約 CPU 資源。因為創建和銷毀 CPU 也會浪費時間,我們希望當有新 goroutine 創建時,立刻能有 M 運行它,如果銷毀再新建就增加了時延,降低了效率。當然也考慮了過多的自旋線程是浪費 CPU,所以系統中最多有 GOMAXPROCS 個自旋的線程 (當前例子中的 GOMAXPROCS=4,所以一共 4 個 P),多余的沒事做線程會讓他們休眠。
(10) 場景 10
? 假定當前除了 M3 和 M4 為自旋線程,還有 M5 和 M6 為空閑的線程 (沒有得到 P 的綁定,注意我們這里最多就只能夠存在 4 個 P,所以 P 的數量應該永遠是 M>=P, 大部分都是 M 在搶占需要運行的 P),G8 創建了 G9,G8 進行了阻塞的系統調用,M2 和 P2 立即解綁,P2 會執行以下判斷:如果 P2 本地隊列有 G、全局隊列有 G 或有空閑的 M,P2 都會立馬喚醒 1 個 M 和它綁定,否則 P2 則會加入到空閑 P 列表,等待 M 來獲取可用的 p。本場景中,P2 本地隊列有 G9,可以和其他空閑的線程 M5 綁定。

(11) 場景 11
G8 創建了 G9,假如 G8 進行了非阻塞系統調用。

? M2 和 P2 會解綁,但 M2 會記住 P2,然后 G8 和 M2 進入系統調用狀態。當 G8 和 M2 退出系統調用時,會嘗試獲取 P2,如果無法獲取,則獲取空閑的 P,如果依然沒有,G8 會被記為可運行狀態,并加入到全局隊列,M2 因為沒有 P 的綁定而變成休眠狀態 (長時間休眠等待 GC 回收銷毀)
- 概述
- go語言基礎特性
- Go語言聲明
- Go項目構建及編譯
- go command
- 程序設計原則
- Go基礎
- 變量
- 常量
- iota
- 基本類型
- byte和rune類型
- 類型定義和類型別名
- 數組
- string
- 高效字符串連接
- string底層原理
- 運算符
- new
- make
- 指針
- 下劃線 & import
- 語法糖
- 簡短變量申明
- 流程控制
- ifelse
- switch
- select
- select實現原理
- select常見案例
- for
- range
- range實現原理
- 常見案例
- range陷阱
- Goto&Break&Continue
- Go函數
- 函數
- 可變參數函數
- 高階函數
- init函數和main函數
- 匿名函數
- 閉包
- 常用內置函數
- defer
- defer常見案例
- defer規則
- defer與函數返回值
- defer實現原理
- defer陷阱
- 數據結構
- slice
- slice內存布局
- slice&array
- slice底層實現
- slice陷阱
- map
- Map實現原理
- 集合
- List
- Set
- 線程安全數據結構
- sync.Map
- Concurrent Map
- 面向對象編程
- struct
- 匿名結構體&匿名字段
- 嵌套結構體
- 結構體的“繼承”
- struct tag
- 行為方法
- 方法與函數
- type Method Value & Method Expressions
- interface
- 類型斷言
- 多態
- 錯誤機制
- error
- 自定義錯誤
- panic&recover
- reflect
- reflect包
- 應用示例
- DeepEqual
- 反射-fillObjectField
- 反射-copyObject
- IO
- 讀取文件
- 寫文件
- bufio
- ioutil
- Go網絡編程
- tcp
- tcp粘包
- udp
- HTTP
- http服務
- httprouter
- webSocket
- go并發編程
- Goroutine
- thread vs goroutine
- Goroutine任務取消
- 通過channel廣播實現
- Context
- Goroutine調度機制
- goroutine調度器1.0
- GMP模型調度器
- 調度器竊取策略
- 調度器的生命周期
- 調度過程全解析
- channel
- 無緩沖的通道
- 緩沖信道
- 單向信道
- chan實現原理
- 共享內存并發機制
- mutex互斥鎖
- mutex
- mutex原理
- mutex模式
- RWLock
- 使用信道處理競態條件
- WaitGroup
- 工作池
- 并發任務
- once運行一次
- 僅需任意任務完成
- 所有任務完成
- 對象池
- 定時器Timer
- Timer
- Timer實現原理
- 周期性定時器Ticker
- Ticker對外接口
- ticker使用場景
- ticker實現原理
- ticker使用陷阱
- 包和依賴管理
- package
- 依賴管理
- 測試
- 單元測試
- 表格測試法
- Banchmark
- BDD
- 常用架構模式
- Pipe-filter pattern
- Micro Kernel
- JSON
- json-內置解析器
- easyjson
- 性能分析
- gc
- 工具類
- fmt
- Time
- builtin
- unsafe
- sync.pool
- atomic
- flag
- runtime
- strconv
- template