最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

Go語言的GMP原理與調(diào)度

2022-10-27 10:31 作者:苦茶今天斷更了嗎  | 我要投稿

GMP 原理與調(diào)度

(1) 單進(jìn)程時代不需要調(diào)度器:一切的程序只能串行發(fā)生。

(2) 多進(jìn)程 / 線程時代有了調(diào)度器需求

一個進(jìn)程阻塞,cpu可以立刻切換到其他進(jìn)程中去執(zhí)行,而且調(diào)度cpu的算法可以保證在運(yùn)行的進(jìn)程都可以被分配到 cpu 的運(yùn)行時間片。從宏觀來看,似乎多個進(jìn)程同時被運(yùn)行。

?


(3) 協(xié)程來提高CPU利用率

為每個任務(wù)都創(chuàng)建一個線程是不現(xiàn)實的,因為會消耗大量的內(nèi)存。

一個線程分為“內(nèi)核態(tài)”線程和“用戶態(tài)”線程。

一個 “用戶態(tài)線程”必須要綁定一個“內(nèi)核態(tài)線程”,CPU并不知道有“用戶態(tài)線程”。

細(xì)分:內(nèi)核線程叫“線程(thread)”,用戶線程叫 “協(xié)程(co-routine)”。

有 3 協(xié)程和線程的映射關(guān)系:

①N:1關(guān)系:

N個協(xié)程綁定1個線程,優(yōu)點(diǎn)就是協(xié)程在用戶態(tài)線程即完成切換,不會陷入到內(nèi)核態(tài),這種切換非常的輕量快速。但也有很大的缺點(diǎn),1個進(jìn)程的所有協(xié)程都綁定在1個線程上。

缺點(diǎn):

某個程序用不了硬件的多核加速能力;

一旦某協(xié)程阻塞,造成線程阻塞,本進(jìn)程的其他協(xié)程都無法執(zhí)行了,根本就沒有并發(fā)的能力了。

?

②1:1關(guān)系

最容易實現(xiàn)。協(xié)程的調(diào)度都由CPU完成了,不存在N:1缺點(diǎn)。

缺點(diǎn):協(xié)程的創(chuàng)建、刪除和切換的代價都由 CPU 完成,有點(diǎn)略顯昂貴了。

?

③M:N關(guān)系

M個協(xié)程綁定N個線程,克服了以上2種模型的缺點(diǎn),但實現(xiàn)起來最為復(fù)雜。

協(xié)程跟線程是有區(qū)別的,線程由 CPU 調(diào)度是搶占式的,協(xié)程由用戶態(tài)調(diào)度是協(xié)作式的,一個協(xié)程讓出 CPU 后,才執(zhí)行下一個協(xié)程。

?

?

Go 語言的協(xié)程 goroutine占用內(nèi)存更小(幾 kb)、調(diào)度更靈活 (runtime 調(diào)度)

被廢棄的 goroutine 調(diào)度器

Go目前使用的調(diào)度器是2012年重新設(shè)計的。

大部分文章都是會用 G來表示Goroutine,用M來表示線程。

實現(xiàn)過程:

M想要執(zhí)行、放回G都必須訪問全局G隊列,并且M有多個,即多線程訪問同一資源需要加鎖進(jìn)行保證互斥 / 同步,所以全局G隊列是有互斥鎖進(jìn)行保護(hù)的。

?

缺點(diǎn):

創(chuàng)建、銷毀、調(diào)度G都需要每個M獲取鎖,這就形成了激烈的鎖競爭。

M轉(zhuǎn)移G會造成延遲和額外的系統(tǒng)負(fù)載。比如當(dāng)G中包含創(chuàng)建新協(xié)程的時候,M創(chuàng)建了G’,為了繼續(xù)執(zhí)行G,需要把G’交給M’執(zhí)行,也造成了很差的局部性,因為G’和G是相關(guān)的,最好放在M上執(zhí)行,而不是其他 M’。

系統(tǒng)調(diào)用 (CPU在M之間的切換) 導(dǎo)致頻繁的線程阻塞和取消阻塞操作增加了系統(tǒng)開銷。

?

Goroutine 調(diào)度器的 GMP 模型的設(shè)計思想

在新調(diào)度器中,出列 M (thread) 和 G (goroutine),又引進(jìn)了P (Processor)。

Processor,它包含了運(yùn)行g(shù)oroutine的資源,如果線程想運(yùn)行g(shù)oroutine,必須先獲取P,P中還包含了可運(yùn)行的G隊列。

?

GMP 模型

在Go中,線程是運(yùn)行g(shù)oroutine的實體,調(diào)度器的功能是把可運(yùn)行的goroutine分配到工作線程上。

全局隊列(Global Queue):存放等待運(yùn)行的 G。

?

P的本地隊列:存放的也是等待運(yùn)行的G,存的數(shù)量有限,不超過256個。新建G’時,G’優(yōu)先加入到P的本地隊列,如果隊列滿了,則會把本地隊列中一半的G移動到全局隊列。

P列表:所有P都在程序啟動時創(chuàng)建,并保存在數(shù)組中,最多有GOMAXPROCS(可配置)。P的數(shù)量:由啟動時環(huán)境變量 $GOMAXPROCS ?或 由runtime的方法GOMAXPROCS()決定。

P何時創(chuàng)建:在確定了P的最大數(shù)量n后,運(yùn)行時系統(tǒng)會根據(jù)這個數(shù)量創(chuàng)建n個P。

?

M:線程想運(yùn)行任務(wù)就得獲取P,從P的本地隊列獲取G,P隊列為空時,M也會嘗試從全局隊列拿一批G放到P的本地隊列,或從其他P的本地隊列偷一半放到自己P的本地隊列。M運(yùn)行G,G執(zhí)行之后,M會從P獲取下一個G,不斷重復(fù)下去。

?

M的數(shù)量go 程序啟動時,會設(shè)置 M 的最大數(shù)量,默認(rèn)10000。但是內(nèi)核很難支持這么多的線程數(shù),所以這個限制可以忽略。

runtime/debug中的SetMaxThreads 函數(shù),設(shè)置M的最大數(shù)量

一個M阻塞了,會創(chuàng)建新的M。

M與P的數(shù)量沒有絕對關(guān)系,一個M阻塞,P就會去創(chuàng)建或者切換另一個M,所以,即使P的默認(rèn)數(shù)量是1,也有可能會創(chuàng)建很多個M出來。

?

M何時創(chuàng)建:沒有足夠的M來關(guān)聯(lián)P并運(yùn)行其中的可運(yùn)行的G,而P中還有很多就緒任務(wù),就會去尋找空閑的M,而沒有空閑的,就會去創(chuàng)建新的M。

?

?

調(diào)度器的設(shè)計策略

復(fù)用線程:避免頻繁的創(chuàng)建、銷毀線程,而是對線程的復(fù)用。

1)work stealing 機(jī)制

當(dāng)本線程無可運(yùn)行的G時,嘗試從其他線程綁定的P偷取G,而不是銷毀線程。

?

2)hand off 機(jī)制

當(dāng)本線程因為G進(jìn)行系統(tǒng)調(diào)用阻塞時,線程釋放綁定的P,把P轉(zhuǎn)移給其他空閑的線程執(zhí)行。

?

利用并行:GOMAXPROCS設(shè)置P的數(shù)量,最多有 GOMAXPROCS個線程分布在多個CPU上同時運(yùn)行。GOMAXPROCS也限制了并發(fā)的程度,比如 GOMAXPROCS =核數(shù)/2,則最多利用了一半的CPU核進(jìn)行并行。

搶占在coroutine中要等待一個協(xié)程主動讓出CPU才執(zhí)行下一個協(xié)程,在Go中,一個 goroutine最多占用CPU 10ms,防止其他goroutine被餓死,這就是區(qū)別。

全局G隊列在新的調(diào)度器中依然有全局G隊列,但功能弱化,當(dāng)M執(zhí)行work stealing從其他P偷不到G時,它可以從全局G隊列獲取G。

?

go func (?) 調(diào)度流程


?1、我們通過go func (?)來創(chuàng)建一個goroutine;

2、有兩個存儲G的隊列,一個是局部調(diào)度器P的本地隊列、一個是全局G隊列。新創(chuàng)建的G會先保存在P的本地隊列中,如果P的本地隊列已經(jīng)滿了就會保存在全局的隊列中;

?

3、G只能運(yùn)行在M中,一個M必須持有一個P,M與 P是 1:1的關(guān)系。M會從P的本地隊列彈出一個可執(zhí)行狀態(tài)的G來執(zhí)行,如果P的本地隊列為空,就會想其他的MP組合偷取一個可執(zhí)行的G來執(zhí)行;

?

4、一個M調(diào)度G執(zhí)行的過程是一個循環(huán)機(jī)制;

?

5、當(dāng)M執(zhí)行某一個G時候,如果發(fā)生了syscall或者其余阻塞操作,M會阻塞,如果當(dāng)前有一些G在執(zhí)行,runtime會把這個線程M從P中摘除 (detach),然后再創(chuàng)建一個新的操作系統(tǒng)的線程 (如果有空閑的線程可用就復(fù)用空閑線程) 來服務(wù)于這個P;

?

6、當(dāng)M系統(tǒng)調(diào)用結(jié)束時候,這個G會嘗試獲取一個空閑的P執(zhí)行,并放入到這個P的本地隊列。如果獲取不到P,那么這個線程M變成休眠狀態(tài),加入到空閑線程中,然后這個G會被放入全局隊列中。

?

調(diào)度器的生命周期

特殊的M0和G0

M0是啟動程序后的編號為0的主線程,這個M對應(yīng)的實例會在全局變量runtime.m0中,不需要在heap上分配,M0負(fù)責(zé)執(zhí)行初始化操作和啟動第一個G,在之后M0就和其他的M一樣了。

G0是每次啟動一個M都會第一個創(chuàng)建的gourtine,G0僅用于負(fù)責(zé)調(diào)度的G,G0不指向任何可執(zhí)行的函數(shù),每個M都會有一個自己的G0。在調(diào)度或系統(tǒng)調(diào)用時會使用G0的棧空間,全局變量的G0是M0的G0。

?

package main

import "fmt"

func main() {

????fmt.Println("Hello world")

}

1.runtime創(chuàng)建最初的線程m0和goroutine g0,并把2者關(guān)聯(lián)。

2.調(diào)度器初始化:初始化m0、棧、垃圾回收,以及創(chuàng)建和初始化由GOMAXPROCS個P構(gòu)成的P列表。

3.示例代碼中的main函數(shù)是main.main,runtime中也有1個main函數(shù)——runtime.main,代碼經(jīng)過編譯后,runtime.main會調(diào)用main.main,程序啟動時會為runtime.main創(chuàng)建 goroutine,稱它為main goroutine吧,然后把main goroutine加入到P的本地隊列

4.啟動m0,m0已經(jīng)綁定了P,會從P的本地隊列獲取G,獲取到main goroutine。

5.G擁有棧,M根據(jù)G中的棧信息和調(diào)度信息設(shè)置運(yùn)行環(huán)境

6.M運(yùn)行G

7.G退出,再次回到M獲取可運(yùn)行的G,這樣重復(fù)下去,直到 main.main 退出,runtime.main 執(zhí)行Defer和Panic處理,或調(diào)用runtime.exit退出程序

調(diào)度器的生命周期幾乎占滿了一個Go程序的一生,runtime.main的goroutine執(zhí)行之前都是為調(diào)度器做準(zhǔn)備工作,runtime.main的goroutine運(yùn)行,才是調(diào)度器的真正開始,直到 runtime.main結(jié)束而結(jié)束。

?

可視化 GMP 編程(略)

方式 1:go tool tracetrace 記錄了運(yùn)行時的信息,能提供可視化的 Web 頁面。

?

方式 2:Debug trace

?


Go 調(diào)度器調(diào)度場景過程全解析

場景1G1創(chuàng)建G2

P擁有G1,M1獲取P后開始運(yùn)行 G1,G1使用go func()創(chuàng)建了G2,為了局部性G2優(yōu)先加入到P1的本地隊列。


場景2:G1執(zhí)行完畢

G1運(yùn)行完成后(函數(shù):goexit),M上運(yùn)行的goroutine切換為G0,G0負(fù)責(zé)調(diào)度時協(xié)程的切換(函數(shù):schedule)。從P的本地隊列取G2,從G0切換到G2,并開始運(yùn)行G2 (函數(shù):execute)。實現(xiàn)了線程M1的復(fù)用。


場景3:G2開辟過多的G

假設(shè)每個P的本地隊列只能存3個G。G2要創(chuàng)建了6個G,前3個G(G3, G4, G5)已經(jīng)加入p1的本地隊列,p1 本地隊列滿了。


?場景4:G2本地滿再創(chuàng)建G7

G2在創(chuàng)建G7的時候,發(fā)現(xiàn)P1的本地隊列已滿,需要執(zhí)行負(fù)載均衡 (把P1中本地隊列中前一半的G,還有新創(chuàng)建G轉(zhuǎn)移到全局隊列)

(實現(xiàn)中并不一定是新的G,如果G是G2之后就執(zhí)行的,會被保存在本地隊列,利用某個老的G替換新G加入全局隊列)

這些G被轉(zhuǎn)移到全局隊列時,會被打亂順序。所以G3,G4,G7被轉(zhuǎn)移到全局隊列。

?


場景5:G2本地未滿創(chuàng)建G8

G2創(chuàng)建G8時,P1的本地隊列未滿,所以G8會被加入到P1的本地隊列。

G8加入到P1點(diǎn)本地隊列的原因還是因為P1此時在與M1綁定,而G2此時是M1在執(zhí)行。所以G2創(chuàng)建的新的G會優(yōu)先放置到自己的M綁定的P上。

?

場景6喚醒正在休眠的M

規(guī)定:在創(chuàng)建G時,運(yùn)行的G會嘗試喚醒其他空閑的P和M組合去執(zhí)行。

假定G2喚醒了M2,M2綁定了P2,并運(yùn)行G0,但P2本地隊列沒有G,M2此時為自旋線程(沒有G但為運(yùn)行狀態(tài)的線程,不斷尋找G)。

場景7:被喚醒的M2從全局隊列取批量G

M2嘗試從全局隊列(簡稱“GQ”) 取一批G放到P2的本地隊列(函數(shù):findrunnable())。M2從全局隊列取的G數(shù)量符合下面的公式:

n = min(len(GQ)/GOMAXPROCS + 1, len(GQ/2))

?

至少從全局隊列取1個 g,但每次不要從全局隊列移動太多的g到p本地隊列,給其他 p留點(diǎn)。這是從全局隊列到P本地隊列的負(fù)載均衡。

?

假定我們場景中一共有4個P(GOMAXPROCS設(shè)置為4,那么我們允許最多就能用4個P來供M使用)。所以M2只從能從全局隊列取1個G(即G3)移動P2本地隊列,然后完成從G0到G3的切換,運(yùn)行G3。


場景8:M2從M1種偷取G

假設(shè)G2一直在M1上運(yùn)行,經(jīng)過2輪后,M2已經(jīng)把G7、G4從全局隊列獲取到了P2的本地隊列并完成運(yùn)行,全局隊列和P2的本地隊列都空了,如場景8圖的左半部分。

全局隊列已經(jīng)沒有G,那m就要執(zhí)行work stealing (偷取):從其他有G的P偷取一半G過來,放到自己的P本地隊列。P2從P1的本地隊列尾部取一半的G,本例中一半則只有1個G8,放到P2的本地隊列并執(zhí)行。

場景9:自旋線程的最大限制

G1本地隊列G5、G6已經(jīng)被其他M偷走并運(yùn)行完成,當(dāng)前M1和M2分別在運(yùn)行G2和G8,M3和M4沒有g(shù)oroutine可以運(yùn)行,M3和M4處于自旋狀態(tài),它們不斷尋找goroutine。

?

為什么要讓m3和m4自旋,自旋本質(zhì)是在運(yùn)行,線程在運(yùn)行卻沒有執(zhí)行G,就變成了浪費(fèi)CPU。為什么不銷毀現(xiàn)場,來節(jié)約CPU資源。因為創(chuàng)建和銷毀CPU也會浪費(fèi)時間,我們希望當(dāng)有新goroutine創(chuàng)建時,立刻能有M運(yùn)行它,如果銷毀再新建就增加了時延,降低了效率。當(dāng)然也考慮了過多的自旋線程是浪費(fèi)CPU,所以系統(tǒng)中最多有GOMAXPROCS 個自旋的線程 (當(dāng)前例子中的GOMAXPROCS=4,所以一共4個P),多余的沒事做線程會讓他們休眠。

?

場景10:G發(fā)生系統(tǒng)調(diào)用/阻塞

假定當(dāng)前除了M3和M4為自旋線程,還有M5和M6為空閑的線程 (沒有得到P的綁定,注意這里最多就只能夠存在4個P,所以P的數(shù)量應(yīng)該永遠(yuǎn)是M>=P,大部分都是M在搶占需要運(yùn)行的P),G8創(chuàng)建了G9,G8進(jìn)行了阻塞的系統(tǒng)調(diào)用,M2和P2立即解綁,P2會執(zhí)行以下判斷:

如果P2本地隊列有G、全局隊列有G或有空閑的M,P2都會立馬喚醒1個M和它綁定,否則P2則會加入到空閑P列表,等待M來獲取可用的P本場景中,P2本地隊列有G9,可以和其他空閑的線程M5綁定。


?場景11:G發(fā)生系統(tǒng)調(diào)用/非阻塞

G8創(chuàng)建了G9,假如G8進(jìn)行了非阻塞系統(tǒng)調(diào)用。

M2和P2會解綁,但M2會記住P2,然后G8和M2進(jìn)入系統(tǒng)調(diào)用狀態(tài)。當(dāng)G8和M2退出系統(tǒng)調(diào)用時,會嘗試獲取P2,如果無法獲取,則獲取空閑的P,如果依然沒有,G8會被記為可運(yùn)行狀態(tài),并加入到全局隊列,M2因為沒有P的綁定而變成休眠狀態(tài) (長時間休眠等待GC回收銷毀)。

?

小結(jié)

Go調(diào)度器很輕量也很簡單,足以撐起 goroutine的調(diào)度工作,并且讓Go具有了原生(強(qiáng)大)并發(fā)的能力。Go 調(diào)度本質(zhì)是把大量的goroutine分配到少量線程上去執(zhí)行,并利用多核并行,實現(xiàn)更強(qiáng)大的并發(fā)。

?


Go語言的GMP原理與調(diào)度的評論 (共 條)

分享到微博請遵守國家法律
吴江市| 亳州市| 吴江市| 同德县| 仁怀市| 和硕县| 广宗县| 邻水| 新巴尔虎左旗| 新野县| 射洪县| 佛山市| 龙州县| 雅安市| 岢岚县| 吉隆县| 澄城县| 胶南市| 娄底市| 资溪县| 大埔县| 江源县| 新巴尔虎左旗| 清新县| 长阳| 洪江市| 北京市| 岳普湖县| 桦南县| 苗栗市| 万宁市| 淳安县| 黄石市| 福清市| 凤凰县| 调兵山市| 扶绥县| 当涂县| 永春县| 新巴尔虎左旗| 区。|