弄懂goroutine排程原理
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協程執行的時間也不是絕對公平的。
如有錯誤地方,還請狂噴