goroutine 1 go的排程器

2022-08-24 07:54:10 字數 3057 閱讀 7579

對多道並行執行的程式來說,有時它要占用處理器執行,有時要等待傳送資訊,當得到資訊後又可繼續執行,乙個程式的執行可能受到另乙個程式的約束。所以程式的執行實際上是走走停停的,為了能正確反映程式執行時的活動規律和狀態變化,引進了程序,以便從變化的角度,動態地分析和研究程式的執行。

用計算機系統來解決某個問題時首先必須編制程式,可把程式看作是具有獨立功能的一組指令的集合,或者說是指出處理器執行操作的步驟。程式的執行必須依賴於乙個實體-資料集

還有乙個問題就是從程式的角度無法正確描述程式執行時的狀態。例:只有乙個「編譯程式」,同時為多個使用者服務,有兩個需要編譯的程式甲、乙,假定編譯程式p從a點開始工作,現在正在編譯程式甲,當工作到b點時,需要把中間結果記錄在磁碟上。於是程式p在b 點等待磁碟傳輸資訊。這時處理器空閒,這時讓p為源程式 乙進行編譯,編譯程式仍從a點開始工作。現在 的問題是如何來描述p的狀態?是說「它在b點等侍磁碟傳輸」還是說「從a點開始執行?

1.程序和執行緒

程序:把乙個程式在乙個資料集上的一次執行稱為乙個程序

(程式是靜止的,程序是動態的。在20世紀80年代,大多數作業系統仍採用程序技術,把程序作為作業系統的基本組成單位。程序既是資源分配單位,又是排程和執行單位)

執行緒:執行緒是程序中可獨立執行的子任務。

(把乙個計算問題或乙個應用問題作為乙個程序,把該程序中可以併發執行的各部分分別作為執行緒。乙個資料庫應用程式的執行建立了乙個資料庫程序,使用者要求從資料庫中生產乙份工資報表,並將它傳送到乙個檔案中。使用者在等待報表生成的時候又向資料庫提出乙個查詢請求。作業系統把使用者的每乙個請求(工資單報表,資料庫查詢)分別作為資料庫程序的乙個執行緒)

為什麼引入執行緒?

1.每個程序都要占用乙個程序控制塊和乙個私有的主存空間,開銷比較大;程序之間傳遞訊息時要從乙個工作區傳到另乙個工作區,需專用通訊機制,速度較慢

2.程序增多就增加了程序排程的次數,給排程和控制帶來了複雜性。

在採用執行緒的作業系統中,程序是資源分配 單位,而執行緒是排程、執行單位。

協程:獨⽴的棧空間,共享堆空間,排程由⽤戶⾃⼰控制,本質上有點類似於

⽤戶級執行緒,這些⽤戶級執行緒的排程也是⾃⼰實現的 。⼀個執行緒上可以跑多個協程,協程是輕量級的執行緒。

程序——>乙個執行緒——>單執行緒程式

程序——>多執行緒——>多執行緒程式

2.併發和並行

1)多執行緒程式在cpu乙個核蕊上執行就是併發

2)多執行緒程式在cpu多個核蕊上執行就是並行

併發是邏輯上的同時發生(simultaneous),而並行是物理上的同時發生。

併發:

並行:

3.使用者空間執行緒和核心空間執行緒之間的對映關係 

n:1,   1:1和   m:n

n:1是說,多個(n)使用者執行緒始終在乙個核心執行緒上跑,context上下文切換確實很快,但是無法真正的利用多核。

1:1是說,乙個使用者執行緒就只在乙個核心執行緒上跑,這時可以利用多核,但是上下文switch很慢。

m:n是說, 多個goroutine在多個核心執行緒上跑,這個看似可以集齊上面兩者的優勢,但是無疑增加了排程的難度。

4.go的排程器

go 的高度器的內部結構:m p g

m:代表真正的核心os執行緒,和posix裡的thread差不多,真正幹活的人

g:代表乙個goroutine,它有自己的棧,instruction pointer和其他資訊(正在等待的channel等等),用於排程。

p:代表排程的上下文,可以把它看做乙個區域性的排程器,使go**在乙個執行緒上跑,它是實現從n:1到n:m對映的關鍵。

圖中看,有2個物理執行緒m,每乙個m都擁有乙個context(p),每乙個也都有乙個正在執行的goroutine。

p的數量可以通過gomaxprocs()來設定,它其實也就代表了真正的併發度,即有多少個goroutine可以同時執行。

圖中灰色的那些goroutine並沒有執行,而是出於ready的就緒態,正在等待被排程。p維護著這個佇列(稱之為runqueue),

go語言裡,啟動乙個goroutine很容易:go function 就行,所以每有乙個go語句被執行,runqueue佇列就在其末尾加入乙個

goroutine,在下乙個排程點,就從runqueue中取出(如何決定取哪個goroutine?)乙個goroutine執行。

為何要維護多個上下文p?因為當乙個os執行緒被阻塞時,p可以轉而投奔另乙個os執行緒!

圖中看到,當乙個os執行緒m0陷入阻塞時,p轉而在os執行緒m1上執行。排程器保證有足夠的執行緒來執行所以的context p。

圖中的m1可能是被建立,或者從執行緒快取中取出。

當mo返回時,它必須嘗試取得乙個context p來執行goroutine,一般情況下,它會從其他的os執行緒那裡steal偷乙個context過來,

如果沒有偷到的話,它就把goroutine放在乙個global runqueue裡,然後自己就去睡大覺了(放入執行緒快取裡)。contexts們也會週期性的檢查global runqueue,否則global runqueue上的goroutine永遠無法執行。

另一種情況是p所分配的任務g很快就執行完了(分配不均),這就導致了乙個上下文p閒著沒事兒幹而系統卻任然忙碌。但是如果global runqueue沒有任務g了,那麼p就不得不從其他的上下文p那裡拿一些g來執行。一般來說,如果上下文p從其他的上下文p那裡要偷乙個任務的話,一般就『偷』run queue的一半,這就確保了每個os執行緒都能充分的使用。

參考:

Goroutine的排程模型

當前有兩個p,各自繫結了乙個m,每個p上掛了乙個本地goroutine佇列,也有乙個全域性goroutine佇列。流程 每次使用go關鍵字宣告時,乙個g物件被建立並加入到本地g佇列或者全域性g佇列。檢查是否有空閒的p 處理器 若有那麼建立乙個m 若有正在sleep的m那麼直接喚醒它 與其繫結,然後這...

Goroutine的排程分析 一

golang這個新興的語言,最關鍵的就在於goroutine,而goroutine的排程又是golang的核心。可以說,沒有goroutine,那麼這個語言就會毫無意義,沒有發明的必要。為了能夠更好的寫出高質量的 最近學習了goroutine的排程,收穫良多。寫篇文章總結記錄一下。analysis ...

golang的goroutine排程機制

感覺豁然開朗,受益匪淺 去繁就簡,再加上自己的一些理解,整理了一下 排程器 主要基於三個基本物件上,g,m,p 定義在原始碼的src runtime runtime.h檔案中 1.g代表乙個goroutine物件,每次go呼叫的時候,都會建立乙個g物件 2.m代表乙個執行緒,每次建立乙個m的時候,都...